Redux, when to split reducers? - reactjs

I have a Redux store with a reducer consisting of 9 case statements. Six of these cases deal with populating an array in the state white three deal with sorting said array, thus there is no natural way of splitting the reducer, is it ok to leave the reducer as one?

The number of case statements isn't the right metric for deciding whether you should split your reducer. Rather, it's how deeply nested is the state slice that your reducer action handler is working on. If its too deep, the code could explode.
Lets say you have this state shape:
{
cart: {
products: [ {name:"shirt"}, {name:"pants"} ],
checkout: false,
}
}
In a single/global reducer setup, you might have this action handler:
//reducer.js
case ADD_CART_PRODUCT:
const {newProduct} = payload
const {cart: {products}} = state
return {...state, cart: {...state.cart, products: [...products, newProduct] } }
Not bad, but you can split off a cartReducer for action handlers that affects the cart state slice, shrinking your code to this:
//cartReducer.js
case ADD_CART_PRODUCT:
const {newProduct} = payload
const {products} = state //this is actually state.cart from the global perspective
return {...state, products: [...products, newProduct] } }

Yes, it is okay to leave the reducer as one. Lets say if I have a reducer for User, I would consider splitting the reducer if it got too big to manage or if it had nothing to do with User and that was my first one. You only have 9 switch cases and that should be fine.

Related

How do you implement multiple tabs for a Redux slice that's associated with a component?

https://redux.js.org/tutorials/essentials/part-2-app-structure#redux-slices
How do you implement multiple tabs for a Redux slice that's associated with a component? I have a component that's associated to 1 Redux slice, but now I need to refactor the whole code to insure that each tab of that component is associated with its own slice. So instead of a 1 to 1 relationship, we have a many to many relationship where each tab has its own unique slice. How do you implement it without changing the redux reducers?
One way of doing this is to copy and paste the slice and create slices called editor_1, editor_2, editor_3, etc. and duplicate every action and reducer x number of times, but that's terrible, but the other method requires a lot of breaking changes and is a lot more difficult.
https://codesandbox.io/s/react-redux-todo-list-bnv8y?from-embed=&file=/src/redux/reducers/todos.js
A simple example would be a todo redux store and having to duplicate 10 todo stores on each tab, each tab having its own "store" or "slice" that are independent of one another. What's the best way to go about solving this issue?
You don't need any duplication in your code. Instead use a higher order reducer and a dynamic state shape like:
{
[k: NameSpace]: ITabState;
}
Then create your reducer to select the correct namespace
// Higher level reducer for namespacing
export const MultiReducer = (state = {}, action) => {
if (!action.payload || !action.payload.namespace) {
return state; // Probably something is wrong
}
switch (action.type) {
// Stuff to handle
case actionTypeKeys.DO_A_THING:
return {
...state,
[action.payload.namespace]: {
...singleReducer(state[action.payload.namespace], action),
},
};
default:
return state;
}
};
Edit:
Your state would be something like
{
tab1: { todos: [] },
tab2: { todos: [] },
tab3: { todos: [] },
}

fundamental redux concept- how to actions launch reducers

to make things simple consider these two reducers(written in typescript):
export type userActionTypes =
| Interface1
| Interface2
const initialState1 = {...//some state} //<--comment indicates some sort of values are present
const initialState2 = {...//some state}
const reducer1 = (state = initialState1, action: userActionTypes){
switch(action.type) {
case action1.case1:
return {...//some new state1}
default: return state
}
}
const reducer2 = (state = initialState2, action: userActionTypes){
switch(action.type){
case action2.case2:
return {...//some new state1}
default: return state
}
}
const rootReducer = combineReducers({
mReducer1: reducer1,
mReducer2: reducer2
})
now say somewhere we call the following in our code:
newAction = {type: action2.type2, //some other values}
dispatch(newAction);
**my question is: ** How does react know which reducer to call? I mean, it doesn't pass the action to every reducer there is in the rootReducer, does it? I mean if that was the case, then all the default cases would be meaningless and all the cases in the case statements would have to be unique. That is not possible, is it?
How does react know which reducer to call?
How does react know which reducer to call? I mean, it doesn't pass the
action to every reducer there is in the rootReducer, does it?
Trick question, redux actually calls all of your reducers.
Consider your root reducer:
const rootReducer = combineReducers({
mReducer1: reducer1,
mReducer2: reducer2
})
This creates a reducer function tree. When an action is dispatched to the store it calls the root reducer and passes the current state and the current action. The reducers in turn recursively call their nested combined reducers, passing state and action until they reach a leaf where you hit a reducer function and compute their next state. They either have a case to handle action or the return the default case which is simply their current state. The recursion goes back up, returning each next state slice piece, combined at each level until you arrive back at the root reducer which returns the entire next state object.
I mean if that was the case, then all the default cases would be
meaningless and all the cases in the case statements would have to be
unique. That is not possible, is it?
Remember that each reducer function is operating on only its little slice of state, not the entire state object. The default case is there for the reducer to return its current state value since there is no work for it to do. All the reducer cases within a reducer function should be unique. If two actions trigger the same state update then they should be grouped
case "case1":
case "case13":
// both cases apply the same update

Clarification on Stores in Reactjs-Redux

I am currently learning how to use Redux in my ReactJS application and all the tutorials I've seen lead me to believe that there can only be one store in Redux. In a real-world applications, you may have multiple streams of data coming from a REST api or some other source. So, for example, I may be looking at a list of customers in one component and a list of projects in another and list of users in yet another. My question is: Does Redux allow you to create a store for each entity with it's own reducer and actions? For example, a customer store, a project store and user store? If not how would one organize this in a single store without everything getting convoluted? Thanks.
* EDIT *
So my state would look something like:
let initalState={
customers:{
data:[]
...
},
projects:{
data:[]
...
}
users:{
data:[]
...
}
}
I think that combinereducers is what you are looking for.
As your app grows more complex, you'll want to split your reducing function into separate functions, each managing independent parts of the state.
The combineReducers helper function turns an object whose values are different reducing functions into a single reducing function you can pass to createStore.
Imagine you want to manage the customers, projects and users.
You will get one reducer per feature :
const customersInitialState = {data:[], ...}
export default function customersReducer(state = customersInitialState , action) {
switch (action.type) {
case 'ADD':
return {...state, data: [...state.data, action.payload]}
default:
return state
}
}
Then you will combine all theses reducers into a single one
export default combineReducers({
customers: customersReducer,
projects: projectsReducer,
users: usersReducer
})
const store = createStore(reducer)
Finally the state of your store will be like this :
{
customers: {data: [], ...},
projects: {data: [], ...},
users: {data: [], ...},
}

Updating string in redux store

I am using redux with react. I have a key in store whose value is a string.
This is what is get in store.getState();
{appBgColor: "#FFF"}
I am updating the store like this.
store.dispatch( changeAppBgColor("#FC0") );
Reducer
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_APP_BG:
return { ...state, appBgColor: action.payload};
default:
return state;
}
};
Everything works fine, the appBgColor get changed whenever a new dispatch happens.
Problem
I have read that to change a key in redux store you should use concat, slice or join to make the state immutable. So I doubt I am doing it wrong. Is this the correct way to do it?
For your use-case of updating the field appBgColor within your state, your reducer implementation is correct.
Note that when you return { ...state, appBgColor: action.payload}, you are not mutating the state, but in fact, creating a copy of the existing state, applying the change and returning it. This makes sure that the state is immutable, ie it is not directly modified.
You would only need to use functions like slice, concat etc when you are updating nested items within your state. For eg, when you need to remove an element from an array within your state, you would need to use slice as shown below.
const index = state.findIndex(a => a.id === action.id)
return [
...state.slice(0, index), ...state.slice(index + 1)
]

Combine Reducers without their functional keys

I have been working on a React / Redux Application, where now the reducer for single feature has become big enough for the thought of dividing the reducer further.
So I was thinking is there a way to divide the reducer into two different reducers and combining them without adding keys of their functional names.
For Ex:
const nowShowing = function(state = {}, action){
switch(action.type){
case types.NS_MOVIES:
return Object.assign({}, state, { nowShowingMovies: action.data })
}
const comingSoon = function(state = {}, action){
switch(action.type){
case types.CS_MOVIES:
return Object.assign({}, state, { comingSoonMovies: action.data })
}
I am looking to check if its possible to combine these reducers
const movies = combineReducers({
nowShowing,
comingSoon
})
in such a way that i can access them in the following manner
mapStateToProps(state){
nowShowing: state.movies.nowShowing,
comingSoon: state.movies.comingSoon
}
The reason i am looking for a solution like this is because, it will allow my entire application to work as earlier, but the nowShowing & comingSoon reducer become separate and my code becomes more modular.
I did try returning an object instead of combining the reducer using the combineReducers function. But that causes the entire state of the reducer to reset when there is a change in value as it recreates the object.
I think this kind of solution is not possible or it should not be attempted. But I would to understand more why, as I do agree that this points to more over architectural flaw in constructing the reducers in the first place.
Any help would be highly appreciated.
Thanks

Resources