Search code examples
reactjsreduxreact-reduxredux-toolkitredux-thunk

createListenerMiddleware to trigger when extraReducer has job done?


I have a Redux createListenerMiddleware defined in store.ts:

export const listenerMiddleware = createListenerMiddleware()

listenerMiddleware.startListening({
    actionCreator: myAction,
    effect: (action, listenerApi) => {
      //  do job
    }
})

Then myAction action is defined in seprate MyAction-slice.ts as follows:

const applicationSlice = createSlice({
    name: 'app',
    initialState: initialState,
    reducers: {
        myAction(state) {
            // do pure functional code here
        },
    }
})

And it works fine when I call myAction from a React component (first myAction did its job, then createListenerMiddleware for that particular action has been fired, e.g:

return (
    <>
        <button onClick={() => dispatch(myAction())}>
    </>
)

However I also have a createAsyncThunk defined in MyAction-slice.ts as follows:

export const myAsyncAction = createAsyncThunk(
    'app/async',
    async () => {
        const promiseData = await axios.get(`https://localhost:8888/hello`)
        // handle errors for promiseData
        return promiseData
    }
)

And then I declare that thunk inside createSlice as follows:

...
extraReducers: (builder) => {
     builder
        .addCase(myAsyncAction.fulfilled, (state, payload) => {
            // change state with a pure functional way
        })
...

And also at the end of my MyAction-slice.ts file I export actions as:

export const {myAction} = applicationSlice.actions

My problem is that I wish to somehow "hook" to myAsyncAction.fulfilled reducer using createListenerMiddleware, the same way I did for myAction reducer. Is it even possible?


Solution

  • Your myAsyncAction.fulfilled is also an ActionCreator.

    Add that to your MyAction-slice.ts after createAsyncThunk:

    export const myAsyncActionCreator = myAsyncAction.fulfilled as AsyncThunkFulfilledActionCreator<AxiosResponse<any, any>, AsyncThunkConfig>
    

    Also replace myAsyncAction.fulfilled to myAsyncActionCreator in your extraReducers.

    Then your createListenerMiddleware would look like (store.ts):

    export const listenerMiddleware = createListenerMiddleware()
    
    listenerMiddleware.startListening({
        actionCreator: myAsyncActionCreator,
        effect: (action, listenerApi) => {
          //  do job
        }
    })
    

    Also don't forget to prepend this listener to your configureStore right after your reducers list:

    export const store = configureStore({
    reducer: {...}
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware().prepend(listenerMiddleware.middleware),
    })
    

    Following same idea, you could also create myAsyncAction.rejected action creator and export it in a same manner and use as:

    listenerMiddleware.startListening({
      actionCreator: myAsyncActionCreatorRejected