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

2

32

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
    })
Coquetry answered 21/8, 2020 at 4:52 Comment(0)
T
34

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
})
Tigon answered 21/8, 2020 at 12:42 Comment(4)
did you mean "await dispatch(updateDataAndGet (params))" instead of "await dispatch(updateData(params))" hope this was a typo ?Reclusion
these are two different example solutions. The first references OP's updateData thunk and getData thunk separately. The second combines them into one new thunk of name updateDataAndGetTigon
Fyi, the only big reason to use createSlice is that it allows for mutations inside your reducer code, thanks to Immer, so you don't have to use pure functions as with the traditional reducers. The result is still an immutable store, but with a wrapper to allow for and convert impure functions.Charlotte
@Charlotte I am a Redux Toolkit maintainer, I know what createSlice does. Yes, you can modify state in there, but that is still "pure" in the sense that it does not have any side effect outside of stuff that is explicitly put into the function as an argument. dispatch is never allowed in a Redux reducer, even in a createSlice reducer. It is a side effect.Tigon
L
39

Possibly it's not actual and the question is outdated, but there is thunkAPI as second parameter in payload creator of createAsyncThunk, so it can be used like so

export const updateData = createAsyncThunk('data/update', async (params, {dispatch}) => {
  const result = await sdkClient.update({ params })
  dispatch(getData())
  return result
})
Limann answered 8/7, 2021 at 8:38 Comment(0)
T
34

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
})
Tigon answered 21/8, 2020 at 12:42 Comment(4)
did you mean "await dispatch(updateDataAndGet (params))" instead of "await dispatch(updateData(params))" hope this was a typo ?Reclusion
these are two different example solutions. The first references OP's updateData thunk and getData thunk separately. The second combines them into one new thunk of name updateDataAndGetTigon
Fyi, the only big reason to use createSlice is that it allows for mutations inside your reducer code, thanks to Immer, so you don't have to use pure functions as with the traditional reducers. The result is still an immutable store, but with a wrapper to allow for and convert impure functions.Charlotte
@Charlotte I am a Redux Toolkit maintainer, I know what createSlice does. Yes, you can modify state in there, but that is still "pure" in the sense that it does not have any side effect outside of stuff that is explicitly put into the function as an argument. dispatch is never allowed in a Redux reducer, even in a createSlice reducer. It is a side effect.Tigon

© 2022 - 2024 — McMap. All rights reserved.