Search code examples
reactjspromiseredux-toolkitunhandled-promise-rejection

redux-toolkit fetch many data in createAsyncThunk


I need function in redux-toolkit to fetch all data from others slices.

I have this code:

export const getAllData = createAsyncThunk(
  'fetchRoot/getAllData',
  async (_, { dispatch, rejectWithValue }) => {
    const promises = [dispatch(getUsers()), dispatch(getSettings()), dispatch(getClients())];

    Promise.all(promises)
      .then((res: any) => {
        // for (const promise of res) {
        //  console.log('SSS', promise);
        //  if (promise.meta.rejectedWithValue) {
        //    return rejectWithValue(promise.payload);
        //  }
        }
      })
      .catch((err) => {
        console.log(err);
      });
  }
);

My question: if one of slice fetch function (example: getUsers()) is rejected, how to reject promise.all?

getUsers() function and extraReducers:

export const getUsers = createAsyncThunk('users/getUsers', async (_, { rejectWithValue }) => {
  try {
    const res = await agent.Users.getAll();
    return await res.data;
  } catch (err) {
    return rejectWithValue(err);
  }
});
  extraReducers: (builder) => {
    builder
      // GetUsers lifecycle ===================================
      .addCase(getUsers.pending, (state) => {
        state.apiState.loading = true;
        state.apiState.error = null;
      })
      .addCase(getUsers.fulfilled, (state, { payload }) => {
        state.apiState.loading = false;
        state.data = payload;
      })
      .addCase(getUsers.rejected, (state, { payload }) => {
        state.apiState.loading = false;
        state.apiState.error = payload;
      })

Solution

  • You have it basically right. Once the Promise.all(promises) has resolved you will have an array containing the resolved value of each of your individual thunks.

    The individual promises will always resolve and will never reject. They will resolve to either a fulfilled action or a rejected action. In some cases, it will make sense to use the unwrap() property which causes rejected actions to throw errors. But looking at the .meta property will work too.

    You can check your action with the isRejected or isRejectedWithValue functions which serve as type guards, that way you won't have any TypeScript errors when accessing properties like action.meta.rejectedWithValue.

    The hard part here is trying to return rejectWithValue() from inside a loop. I would recommend unwrapping to throw an error instead.

    import { createAsyncThunk, unwrapResult } from "@reduxjs/toolkit";
    
    export const getAllData = createAsyncThunk(
      "fetchRoot/getAllData",
      async (_, { dispatch }) => {
        const promises = [dispatch(getUsers()), dispatch(getSettings()), dispatch(getClients())];
    
        const actions = await Promise.all(promises);
        return actions.map(unwrapResult);
      }
    );
    

    Note that there is no reason to try/catch in your getUsers thunk if you are going to rejectWithValue with the entire caught error object. Just let the error be thrown.

    export const getUsers = createAsyncThunk('users/getUsers', async () => {
      const res = await agent.Users.getAll();
      return res.data;
    });