(this is adapted from the redux-happy-async project I made earlier this year)
A "request" is simply a tiny little state machine that lives in a request reducer. It's keyed off of a name (usually an action type) and, optionally, a unique key to help track multiple requests of the same type.
By keeping a request in your Redux state, you can easily trigger a request (through an action creator) in a component and react to the loading/error states of that request in a different component. It is also easy to prevent multiple, identical requests from being in-flight at once.
The request holds the following state:
status: This is eitherREQUEST_IDLE,REQUEST_PENDINGREQUEST_SUCCESS, orREQUEST_ERROR.error: This is an error value supplied by the action creator when the error state is entered.key: This is the unique key of the request, if supplied.
Within a component, you can fetch a request from the state:
import {getRequest} from 'LIB_NAME';
function mapStateToProps(state, props) {
return {
// gets the request for actionTypes.getUser with the unique key props.userId
request: getRequest(state, actionTypes.getUser, props.userId);
};
}Requests are created in action creators:
import {requestStart, requestError, requestSuccess} from 'LIB_NAME';
export function getUser(userId) {
return async (dispatch) => {
dispatch(requestStart(actionTypes.getUser, userId));
const resp = await window.fetch(/* ... */);
if (resp.status !== 200) {
const err = await resp.json();
dispatch(requestError(actionTypes.getUser, userId, error));
return;
}
dispatch(requestSuccess(actionTypes.getUser, userId));
const data = await resp.json();
dispatch({
type: actionTypes.getUser,
user: data,
});
};
}If you attempt to start an already-started request, an error will be thrown.
To reset a request - for example, to ensure that when you exit and return to a page, you do not see state from a past request - simply use:
import {requestReset} from 'LIB_NAME';
dispatch(requestReset(actionTypes.getUser, userId));TODO: can an in-flight request be reset?
They potentially could, but a better way to handle custom state updating would be to simply dispatch another action if an error is encountered.
You can create a counter in your action creator:
let todoRequestId = 0;
export function createTodo(text) {
return async (dispatch) => {
dispatch(requestStart(actionTypes.createTodo, todoRequestId));
// ...
};
}You'll likely want to then get a list of pending requests to be able to reference/display these requests (see below).
You can filter for requests from the async reducer:
const createTodoRequests = store.getState().requests[actionTypes.createTodo];
const pending = createTodoRequests.map((request) => request.status === REQUEST_PENDING);