I’d like to keep track of API requests that I make with react-redux. To do this I’d like to generate a request Id inside the action and pass that along to middleware and reducers through the payload. Then when I’m dispatching the action from my component I can capture the request Id and use it for updating the component as the request progresses.
Here’s some example code
State
export interface State { [requestId: number]: Request; } export interface Request { status: string; error?: string; }
Action
export function createRequest(): Action { return { type: "request", payload: { requestId: Math.random () // Make a random Id here } }; }
Reducer
export function createRequestReducer(state: State): State { return { ...state, ...{ state.payload.requestId: { status: "pending" } } }; }
Component
interface props { getRequestById: (id: number) => Request; createRequest: () => number; } const component = (props: testProps): JSX.Element => { const getRequestById = props.getRequestById; const [requestId, setRequestId] = useState(null); const [request, setRequest] = useState(null); useEffect(() => { if (requestId !== null) { setRequest(getRequestById(requestId)); } }, [requestId]); return <div>The request status is {(request && request.status) || "Not started"}</div>; } function mapStateToProps(state: State) { return { getRequestById: (requestId: number): Request => { getRequestById(state, requestId) } }; } function mapDispatchToProps(dispatch: Dispatch) { return { createRequest: (): number => { const action = createRequest(); dispatch(action); return action.payload.requestId; } }; } export default connect(mapStateToProps, mapDispatchToProps)(component);
I expect this will work but it may be a massive anti pattern. Is this not advised and, if so, is there an alternative?
Advertisement
Answer
I think your approach works technically totally fine. Only “logically” it might make sense to make a some changes:
Yes, the “action” is something that is supposed to be sent to the reducer (and not used anywhere else, although there is technically no problem with that).
But what you can do:
1. separate action and values
Inside the action creator function, you can do whatever you want.
So you can create and use the action
and the requestId
seperately.
This is technically exact the same as what you did, but logically separated.
E.g.:
function createRequest(){ const requestId = createUniqueId(); const action = { type: "request", payload: { requestId: requestId } }; return { requestId: requestId, // <-- request id independent of the action action: action, // <-- action independent of the request id }; } function mapDispatchToProps( dispatch: Dispatch ){ return { createRequest: (): number => { const { requestId, action } = createRequest(); dispatch( action ); // <-- action independent of the request id return requestId; // <-- request id independent of the action } }; }
2. “action dispatchers”
I (and apparently others as well) like to use what I call “action dispatchers”. This is an extra step and more code, but I think when you got used to this concept, it eliminates any doubts where code like that has to be put.
E.g.:
// Create the action, and nothing else: const createRequestActionCreator = function( requestId ){ return { type: "request", payload: { requestId: requestId } }; }; // Preper some data needed to create the action: const createRequestActionDispatcher = function( dispatch ){ return function(){ const requestId = createUniqueId(); dispatch( createRequestActionCreator( requestId ) ); return requestId; }; }; // function mapDispatchToProps( dispatch: Dispatch ) { return { createRequest: (): number => { const requestId = createRequestActionDispatcher( dispatch )(); return requestId; } }; }
2.a
Additionally you could pass such an “action dispatcher” directly as a prop, if you want.
In this case it basically replaces your function in mapDispatchToProps
, but is reusable, e.g.:
function mapDispatchToProps( dispatch: Dispatch ) { return { createRequest: createRequestActionDispatcher( dispatch ), }; }
2.b
Some people prefer to use a fat-arrow-function here, which I find more confusing, not less, but it looks cleaner as soon as you got used to that pattern:
const createRequestActionDispatcher = (dispatch: Dispatch) => (maybeSomeValue: MyType) => { const requestId = createUniqueId(); dispatch( createRequestActionCreator( requestId ) ); return requestId; };
Remark:
I generally prefer to be consistent, for which I should always (or never) use these “action dispatchers”,
but I found that most of the time I don’t need one, but sometimes I find them very useful.
So I’m actually using dispatch( myAction )
in some places and myActionDispatcher(value)(dispatch)
in others.
I don’t like that, but it works well, and I don’t have a better idea.