Search code examples
javascriptreactjsreduxredux-toolkit

How to make one slice call reducer corresponding to another dispatch


I'm using React and I've got a Redux-Toolkit (RTK) state slice that is persisted with localStorage (I'm using the redux-persist library). Simplified it's like that:

const initLocations = []

const locationsPersistentSlice = createSlice({
  {
  name: "locations",
  initLocations,
  reducers: {
    addLocation(prevState, action) {
      // adding new location to store
    },
    clearLocations() {
      return []
    }
  }
})

It works synchronously and perfectly fine.

But, I want to add second slice, locationInfoSlice, that should fetch data when addLocation() action is dispatched, using the very last location in the list.

How can I achieve that? I thought about extraReducers or asyncThunk, but didn't get it right.


Solution

  • But, I want to add second slice locationInfoSlice, that should fetch data when addLocation() action is dispatched, using the very last location in the list.

    If I'm understanding your post/question correctly you effectively want to dispatch some single action and do the following:

    • Store a location in the state.locations array
    • Handle some asynchronous action/logic to fetch some data using the last location in the state.locations array.

    How can I achieve that? I thought about extraReducers or asyncThunk, but didn't get it right.

    You'll likely need to use both. extraReducers alone won't work since reducer functions are pure synchronous functions without side-effects, e.g. they can't fetch data asynchronously. My suggestion would be to create a Thunk that dispatches the addLocation action to update the state.locations state, and also makes the data fetch you need that can return a value that will be used to update the locationInfoSlice's state in extraReducers.

    import { createAsyncThunk } from '@reduxjs/toolkit';
    import { addLocation } from '../locationsPersistentSlice';
    
    export const addLocationAsync = createAsyncThunk(
      "locations/addLocationAsync",
      async (location, thunkApi) => {
        // dispatch to add new location
        thunkApi.dispatch(addLocation(location));
    
        try {
          // asynchronous fetch logic
          return data;
        } catch(error) {
          return thunkApi.rejectWithError(error);
        }
      },
    );
    
    import { createSlice } from '@reduxjs/toolkit';
    
    const initialState = [];
    
    const locationsPersistentSlice = createSlice({
      name: "locations",
      initialState,
      reducers: {
        addLocation(state, action) {
          state.push(action.payload);
        },
        clearLocations() {
          return initialState;
        },
      },
    });
    
    export const {
      addLocation,
      clearLocations,
    } = locationsPersistentSlice.actions;
    
    export default locationsPersistentSlice.reducer;
    
    import { createSlice } from '@reduxjs/toolkit';
    import { addLocationAsync } from './action';
    
    const initialState = /* whatever this initial state value is */;
    
    const locationInfoSlice = createSlice({
      name: "locationInfo",
      initialState,
      extraReducers: builder => {
        builder
          .addCase(addLocationAsync.fulfilled, (state, action) => {
            // update state with fetched data
          })
          .addCase(addLocationAsync.rejected, (state, action) => {
            // update state with error status???
          })
          .addCase(addLocationAsync.pending, (state, action) => {
            // set any pending/loading state
          });
      },
    });
    
    export default locationInfoSlice.reducer;
    

    In the UI instead of dispatching the addLocation action directly it will now dispatch the addLocationAsync action.

    import { useDispatch } from 'react-redux';
    import { addLocationAsync } from './action';
    
    ...
    
    const dispatch = useDispatch();
    
    ...
    
    dispatch(addLocationAsync(/* some location value */));
    
    ...