Search code examples
reduxreact-reduxredux-thunkredux-toolkit

Thunk chaining within a thunk (using Redux toolkit). How to call a thunk when another one finished?


My application allows users to enter data in many different places, and each time I use something like this to call an async thunk:

dispatch(handleDataEntry({ key, value })

In some cases the results of the API call inform the application that another datapoint needs to be set client-side, so the downstream extraReducer in my slice (using createSlice from @reduxjs/toolkit) should now re-dispatch the thunk. But this seems to be an anti-pattern, and in any case I couldn't find a way to do this, so I'm storing sideEffectKey and sideEffectValue in state and having components in cases of non-nullity dispatch the thunk themselves.

This doesn't seem like a good solution, and in fact since sideEffectKey and sideEffectValue stay non-null until the second API call completes and I can clear them, handleDataEntry gets dispatched multiple times, causing me problems.

What's a better solution? Is there some way to do something like this?

[handleDataEntry.fulfilled.toString()]: (
            state: Store,
            action: {
                payload: {
                    sideEffectKey: string | null
                    sideEffectValue: string | null
                }
            }
) => {
  if (action.payload.sideEffectKey) { 
     dispatch(handleDataEntry({ key: sideEffectKey, value: sideEffectValue }))
  }
}

Solution

  • How to dispatch a thunk when another one is finished ?

    How to listen to a thunk status ?

    In your case when you thunk is finish and succeed you dispatch fulfilled event.

    Because create async thunk create actions for theses 3 different states:

    • pending
    • fulfilled
    • rejected

    By default your thunk cannot listen theses actions dispatched by your other thunk. You need a listener middleware to do it

    Listener middleware

    With listener middleware you can listen to any actions to execute some logic.

    You have three different way to listen:

    • one with an actionCreator
    • one with a matcher
    • one with a predicate

    How to use listener middleware ?

    In your case you can add a matcher on this to execute handleDataEntry when it has finished.

    listenerMiddleware.startListening({
      matcher: handleDataEntry.fulfilled,
      effect: async (action, listenerApi) => {
         if (action.payload.sideEffectKey) { 
            listenerApi.dispatch(handleDataEntry({ key: sideEffectKey, value: sideEffectValue }))
         }
        }
      },
    })
    

    Final note

    Just be careful on your case you thunk your thunk to call it itself maybe ad a limit. To make sure to do not have an infinite fetch calls

    Avoiding side effect in reducers

    You are right reducers must be pure and should not have side effects.