I have a ReactJS app in TypeScript
with redux
and I'm using redux-toolkit
to build my reducers. As the app has grown larger, I want to start refactoring my reducers.
My redux state looks like the following:
{
customers: Customers[],
orders: {
state1: SomeIndependentState1,
state2: SomeIndependentState2,
state3: SomeDependentState2,
state4: SomeDependentState3,
}
}
The customers
and orders
slices are independent and I can easily write two separate reducers for them, combining them with combineReducers
later.
Now, I want to break down my orders
reducer further.
state1
and state2
are completely independent. state3
depends on data from state1
.state4
depends on data from state1
and state2
.Is there a way to continue using createReducer
from redux-toolkit
(or some other functionality from the toolkit) to create reducers for each nested slice within the orders
slice?
As I've started rewriting my reducer for orders
, here's what I have so far:
export const ordersReducer = (state: Orders, action: AnyAction) => {
return {
state1: state1Reducer(state?.state1, action),
state2: state2Reducer(state?.state2, action),
state3: {}, // not sure how to write a reducer for this slice and satisfy its dependency on state1
state4: {}, // not sure how to write a reducer for this slice and staisfy its dependency on state1 and state2
}
};
const initialState1: State1 = {};
export const state1Reducer = createReducer(initialState1, (builder) =>
builder
.addCase(...)
.addCase(...)
);
const initialState2: State2 = {};
export const state2Reducer = createReducer(initialState2, (builder) =>
builder
.addCase(...)
.addCase(...)
);
Note: I don't have control over the structure of my redux state. I'm not completely tied to using redux-toolkit
but would need a good justification to have my team move away from it.
How do I share state between two reducers? Do I have to use combineReducers ? The suggested structure for a Redux store is to split the state object into multiple “slices” or “domains” by key, and provide a separate reducer function to manage each individual data slice.
You can use it at all levels of your reducer structure, not just to create the root reducer. It's very common to have multiple combined reducers in various places, which are composed together to create the root reducer.
It turns out that Redux lets us combine multiple reducers into one that can be passed into createStore by using a helper function named combineReducers . The way we combine reducers is simple, we create one file per reducer in the reducers directory. We also create a file called index. js inside the reducers directory.
Just pass everything to the reducer that it needs. In this case, I would pass the whole orders
state instead of passing state1
, state2
, state3
, etc.
export const ordersReducer = (orders: Orders, action: AnyAction) => {
return {
// ...
state3: state3Reducer(orders, action),
// ...
}
};
const initialState3: State3 = {};
export const state3Reducer = createReducer(initialState3, (builder) =>
builder
.addCase(someAction, (orders, action) => orders.state3 + orders.state1) // or anything else
.addCase(...)
);
I renamed state
to orders
. I know that the canonical docs use state
a lot, but that will get very confusing very quickly.
This is of course if state3
depends on the old state1
. If it depends on the new value then you must have all the data that state3
needs in your action and the old state1
. (Which leads back to the above solution). If you do not, your reducers are not pure functions.
Don't stress too much about your reducers. They have to be pure functions but they can have any number and type of args and return anything. They don't always have to strictly match the "relevant state prop to the relevant state prop".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With