Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
    })
like image 442
Joji Avatar asked Aug 21 '20 04:08

Joji


People also ask

Does Redux support multiple actions?

Well, no matter what you pass to dispatch , it is still a single action. Even if your action is an array of objects, or a function which can then create more action objects!

What is createAsyncThunk in Redux toolkit?

createAsyncThunk will generate three Redux action creators using createAction : pending , fulfilled , and rejected . Each lifecycle action creator will be attached to the returned thunk action creator so that your reducer logic can reference the action types and respond to the actions when dispatched.

Can we dispatch actions from reducers?

Dispatching an action within a reducer is an anti-pattern. Your reducer should be without side effects, simply digesting the action payload and returning a new state object. Adding listeners and dispatching actions within the reducer can lead to chained actions and other side effects.

Who is responsible for dispatching the action in Redux?

A Redux app really only has one reducer function: the "root reducer" function that you will pass to createStore later on. That one root reducer function is responsible for handling all of the actions that are dispatched, and calculating what the entire new state result should be every time.


2 Answers

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
})
like image 110
phry Avatar answered Oct 26 '22 09:10

phry


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
})
like image 42
qhor Avatar answered Oct 26 '22 08:10

qhor