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:
- 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.
- 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.
- 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.