Neha Gupta
Neha Gupta

Reputation: 181

How to access state of one slice in reducer of another slice using redux-toolkit

I am trying to access state of another slice in reducer of another slice using getState() method but looks like this is not allowed and hence the web application breaks.

Does anyone know whats the recommended way to access state of a slice inside reducer of another slice? I need this for my web application.

Thanks in advance for your any help

Upvotes: 12

Views: 13202

Answers (1)

Ian A
Ian A

Reputation: 6163

According to the Redux docs, there are 3 different approaches you could use:

Many users later want to try to share data between two reducers, but find that combineReducers does not allow them to do so. There are several approaches that can be used:

  1. If a reducer needs to know data from another slice of state, the state tree shape may need to be reorganized so that a single reducer is handling more of the data.
  2. You may need to write some custom functions for handling some of these actions. This may require replacing combineReducers with your own top-level reducer function. You can also use a utility such as reduce-reducers to run combineReducers to handle most actions, but also run a more specialized reducer for specific actions that cross state slices.
  3. Middleware with async logic such as redux-thunk have access to the entire state through getState(). An action creator can retrieve additional data from the state and put it in an action, so that each reducer has enough information to update its own state slice.

Example

Lets assume we have the following slices:

const namesSlice = createSlice({
  name: "Names",
  initialState: {
    value: [],
    name: "Names"
  },
  reducers: {
    ...
  }
};

const counterSlice = createSlice({
  name: "Counter",
  initialState: {
    value: 0,
    name: "Names"
  },
  reducers: {
    ...
  }
};

const reducer = combineReducers({
  counter: counterReducer,
  names: namesReducer
});

And we want to define an action addName which will add a name (entered by the user) into the names array, but will also append the current value of the counter state onto the name before adding it.

Option 1: Restructure Slices

This involves merging the 2 slices into a single slice, so you'd end up with something like:

const namesSlice = createSlice({
  name: "NamesAndCounter",
  initialState: {
    value: {
      names: [],
      counter: 0
    },
    name: "NamesAndCounter"
  },
  ...
};

which would allow you to access both names and counter in the reducer.

If you don't want to restructure your state/slices, then there are options 2 and 3:

Option 2: Use reduce-reducers

A third party library reduce-reducers can be used

Here you would define a cross slice reducer which is capable of accessing both the counter and names slices:

export const crossSliceReducer = (state, action) => {
  if (action.type === "CROSS_SLICE_ACTION") {
    const newName = action.payload + state.counter.value;
    const namesState = state.names;
    state = {
      ...state,
      names: { ...namesState, value: [...state.names.value, newName] }
    };
  }
  return state;
};

// Combine reducers
const reducer = combineReducers({
  counter: counterReducer,
  names: namesReducer
});

// Add the cross-slice reducer to the root reducer
const rootReducer = reduceReducers(reducer, crossSliceReducer);

// Create store
const store = configureStore({
  reducer: rootReducer
});

Then you can dispatch the following action to invoke the reducer:

dispatch({ type: "CROSS_SLICE_ACTION", payload: name });

Note: The reduce-reducers library is no longer being maintained

Option 3: Use Thunks

Using thunks (meant for async actions like calling an API) allows you to get at the entire state. You can define a thunk that references the getState function that allows you to get at any slice in the global state:

export const addWithThunk = createAsyncThunk(
  "names/addWithThunk",
  async (name, { getState }) => {
    return name + getState().counter.value;
  }
);

Thunks are defined in the extraReducers property of the argument passed to createSlice():

extraReducers: (builder) => {
    builder.addCase(addWithThunk.fulfilled, (state, action) => {
      state.value.push(action.payload);
    });
  }

And can be invoked in the same way you'd invoke a plain action:

dispatch(addWithThunk(name));

There's a CodeSandbox demo showing options 2 and 3. When you add a name using one of the Submit buttons, it will access the counter state and append the current value of the counter to the name you input before adding the name into the names state.

Upvotes: 18

Related Questions