Search code examples
reactjsreact-nativereduxreact-reduxredux-toolkit

Dispatch multiple async actions with Redux Toolkit


I'm building an app with Expo/React Native and using Redux via Redux Toolkit to handle the store/state of the app using slices.

I've been using this setup for a while without complications because my functions are simple (like user/auth, cart, category and products reducers).

But for this app I need to load multiple catalogs from a single endpoint. To achieve this I have created a "catalogs" slice with multiple reducers and a single action which performs the requests and dispatches the reducer depending on the name of the catalog.

const initialState = {
  catalogOne: [],
  catalogTwo: [],
  catalogThree: [],
  catalogN: []
}

const catalogsSlice = createSlice({
  name: "catalogs",
  initialState,
  reducers: {
    catalogOneRequestSucceeded: (state,action) => {
      state.catalogOne = action.payload
    },
    catalogTwoRequestSucceeded: (state,action) => {
      state.catalogTwo = action.payload
    },
    // ... and so on a reducer for each catalog
  }
});

And then I have the following action which is used to request the endpoint with the name of the catalog and dispatch the reducer:

export const catalogRequest = (catalogName) => async (dispatch, getState) => {
  const state = getState();

  try {
    const rsp = await axios.get(`https://catalogEndpointBase/${catalogName}`, {
      headers: {
        Authorization: `Bearer ${state.user.accessToken}`,
        "Content-Type": "application/json",
      },
    });

    switch (catalogName) {
      case "catalogOne":
        dispatch(catalogOneRequestSucceeded(rsp.data));
        break;
      case "catalogTwo":
        dispatch(catalogTwoRequestSucceeded(rsp.data));
        break;
      case "catalogThree":
        dispatch(catalogThreeRequestSucceeded(rsp.data));
        break;
    }

    return rsp.data;
  } catch (e) {
    throw e;
  }
};

So my question now is: how do I dispatch this action multiple times in an "async/await" way so the catalogs are loaded one after the other? and also: am I doing it the right way or is there a better approach to this kind of multiple requests?

I was thinking of something like this, but I don't really know how to accomplish that:

  useEffect(() => {
    const loadCatalogsAsync = async() => {
      try {
        await dispatch(catalogRequest("catalogOne"));
        await dispatch(catalogRequest("catalogTwo"));
        await dispatch(catalogRequest("catalogThree"));
        // ... and so on for all my catalogs
      } catch ( e ) {
        console.error(e);
      }
    }

    loadCatalogsAsync();
  }, []);
}

The goal is to find a best-practice for kind of behavior where the app is required to fetch multiple data when the app is loaded.

Thanks.


Solution

  • You don't need to await the dispatch. You also don’t need a try/catch block because the thunk will catch any API errors and dispatch a “rejected” action. You can just dispatch your three thunks.

    useEffect(() => {
      dispatch(catalogRequest("catalogOne"));
      dispatch(catalogRequest("catalogTwo"));
      dispatch(catalogRequest("catalogThree"));
    }, []);
    

    I would write your thunk and your reducer a lot simpler. I would use one action type and let the catalogName be part of the payload (you can also access it from the meta property of the action). You could delete that entire switch block in your thunk and the corresponding case reducers. Instead you can have one case reducer that updates the correct property in the state based on action.payload.catalogName.

    thunk:

    export const catalogRequest = createAsyncThunk(
      "catalogs/request",
      async (catalogName, { getState }) => {
        const state = getState();
        const rsp = await axios.get(`https://catalogEndpointBase/${catalogName}`, {
          headers: {
            Authorization: `Bearer ${state.user.accessToken}`,
            "Content-Type": "application/json"
          }
        });
        return {
          data: rsp.data,
          catalogName
        };
      }
    );
    

    slice:

    const catalogsSlice = createSlice({
      name: "catalogs",
      initialState,
      reducers: {},
      extraReducers: {
        [catalogRequest.fulfilled]: (state, action) => {
          const { data, catalogName } = action.payload;
          state[catalogName] = data;
        }
      }
    });