Dispatch an action on different stages (pending, rejected, fulfilled) of Async action

Viewed 76

I saw toast implementation in the toolkit docs but it's not what I'd want.

I want to show a snackbar that depends on the current state of async action.

const addUser = createAsyncThunk('users/addUser', async (user: TUser) => {
  const { data } = await axios.post(USERS_ENDPOINTS.POST, user);
  return data;
}
...
extraReducers: (builder) => {
  builder.addCase(addUser.pending, (state, action) => {
    // at this stage I want to show to a user "Adding..." snackbar
    state.loading = true; 
  }
  builder.addCase(addUser.fulfilled, (state, action) => {
    // at this stage I want to show to a user "Added!" snackbar
    state.users = [...state.users, action.payload]
    state.loading = false;
  }
  // so on for each async action
}

I can't dispatch anything inside a reducer, so how do I do that?

Do I wrap async action body inside try catch and dispatch snackbar actions here?

const addUser = createAsyncThunk('users/addUser', async (user: TAddUserDto, dispatch) => {
    try {
      dispatch(pendingSnackbar('Adding user...'));
      const { data } = await axios.post<TUser>(USERS_ENDPOINTS.POST, user);
      dispatch(fulfilledSnackbar('Added!'));

      return data;
    } catch (err) {
      dispatch(rejectedSnackbar('Error!'));
    }
}

I think it would break rejected case in extraReducers.

How can I implement this logic?

1 Answers

Thanks to @OsmanysFuentes-Lombá I came up with a solution.

// vendorSlice.ts
const addVendor = createAsyncThunk(
    'vendors/addVendor',
    // Declare the type your function argument here:
    async (vendor: TCreateVendorDto) => {
        const { data } = await axios.post<TVendor>(VENDORS_ENDPOINTS.POST, vendor);

        // Inferred return type: Promise<MyData>
        return data;
    }
);

// middleware.ts
// create listener middleware
const listenerMiddleware = createListenerMiddleware();

// start listening to `addVendor.pending` state
// when this state occurs, show snackbar 
listenerMiddleware.startListening({
    actionCreator: addVendor.pending,
    effect: (_, { dispatch, unsubscribe }) => {
        dispatch(openSnackbar({ severity: 'info', snackbarText: 'Adding...' }));
        unsubscribe();
    },
});

// same as above
listenerMiddleware.startListening({
    actionCreator: addVendor.fulfilled,
    effect: (_, { dispatch, unsubscribe }) => {
        dispatch(openSnackbar({ severity: 'success', snackbarText: 'Vendor added!' }));
        unsubscribe();
    },
});

// root-store.ts
export const store = configureStore({
    reducer: {
        vendors: vendorsReducer,
    },
    // add listener middleware to root store
    middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware().concat(listenerMiddleware.middleware),
});


Related