Search code examples
reactjsreduxredux-thunkredux-toolkit

Redux ToolKit: is it possible to dispatch other actions from the same slice in one action created by createAsyncThunk


I am using redux-toolkit with createAsyncThunk to handle async requests.

I have two kinds of async operations:

  1. get the data from the API server

  2. update the data on the API server

export const updateData = createAsyncThunk('data/update', async (params) => {
  return await sdkClient.update({ params })
})

export const getData = createAsyncThunk('data/request', async () => {
  const { data } = await sdkClient.request()
  return data
})

And I add them in extraReducers in one slice

const slice = createSlice({
  name: 'data',
  initialState,
  reducers: {},
  extraReducers: (builder: any) => {
    builder.addCase(getData.pending, (state) => {
      //...
    })
    builder.addCase(getData.rejected, (state) => {
      //...
    })
    builder.addCase(
      getData.fulfilled,
      (state, { payload }: PayloadAction<{ data: any }>) => {
        state.data = payload.data
      }
    )
    builder.addCase(updateData.pending, (state) => {
      //...
    })
    builder.addCase(updateData.rejected, (state) => {
      //...
    })
    builder.addCase(updateData.fulfilled, (state) => {
      //<--- here I want to dispatch `getData` action to pull the updated data
    })
  },
})

In my component, I have a button that triggers dispatching of the update action. However I found after clicking on the button, despite the fact that the data is getting updated on the server, the data on the page is not getting updated simultaneously.

function MyComponent() {
  const dispatch = useDispatch()
  const data = useSelector((state) => state.data)

  useEffect(() => {
    dispatch(getData())
  }, [dispatch])

  const handleUpdate = () => {
    dispatch(updateData())
  }

  return (
    <div>
      <ul>
        // data goes in here
      </ul>
      <button onClick={handleUpdate}>update</button>
    </div>
  )
}

I tried to add dispatch(getData()) in handleUpdate after updating the data. However it doesn't work because of the async thunk. I wonder if I can dispatch the getData action in the lifecycle action of updateData i.e.

builder.addCase(updateData.fulfilled, (state) => {
      dispatch(getData())//<--- here I want to dispatch `getData` action to pull the updated data
    })

Solution

  • First of all: please note that reducers always need to be pure functions without side effects. So you can never dispatch anything there, as that would be a side effect. Even if you would somehow manage to do that, redux would warn you about it.

    Now on to the problem at hand.

    You could create a thunk that dispatches & awaits completion of your updateData call and then dispatches your getData call:

    export const updateAndThenGet = (params) => async (dispatch) => {
      await dispatch(updateData(params))
      return await dispatch(getData())
    }
    
    //use it like this
    dispatch(updateAndThenGet(params))
    

    Or if both steps always get dispatched together anyways, you could just consider combining them:

    export const updateDataAndGet = createAsyncThunk('data/update', async (params) => {
      await sdkClient.update({ params })
      const { data } = await sdkClient.request()
      return data
    })