As your application grows, you probably wouldn't want to write all the logic for how the state of your application needs to be transformed in a simple reducer function. What you would probably want is to write smaller reducers that specialize in managing independent parts of the state.
Take for example the following reducer function:
const initialState = { todoList: [], chatMsg: [], } const reducer = (state = initialState, action) => { switch (action.type) { case 'ADD_TODO': return { ...state, todoList: [ ...state.todoList, { title: action.title, completed: action.completed, }, ], } case 'ADD_CHAT_MSG': return { ...state, chatMsg: [ ...state.chatMsg, { from: action.id, message: action.message, }, ], } default: return state } }
You have two properties that manage the state of two different parts of an application. One manages the state of a Todo, list while the other manages the Chat messages. You could split this reducer into two reducer functions, where each manages one slice of the state, for instance:
const initialState = { todoList: [], chatMsg: [], } const todoListReducer = (state = initialState.todoList, action) => { switch (action.type) { case 'ADD_TODO': return state.concat([ { title: action.title, completed: action.completed, }, ]) default: return state } } const chatMsgReducer = (state = initialState.chatMsg, action) => { switch (action.type) { case 'ADD_CHAT_MSG': return state.concat([ { from: action.id, message: action.message, }, ]) default: return state } }
However, because createStore method accepts only one reducer as the first argument, you would need to combine them into a single reducer:
const reducer = (state = initialState, action) => { return { todoList: todoListReducer(state.todoList, action), chatMsg: chatMsgReducer(state.chatMsg, action), } }
In this way, we are able to split our reducers into smaller reducers that specialize in managing only one slice of the state, and later combine them together into a single reducer function.
Redux provides a helper method named combineReducers that allows you to combine reducers in a similar way to what we just did but without having to repeat a lot of code; for instance, we could rewrite the previous way of combining reducers like this:
const reducer = combineReducers({ todoList: todoListReducer, chatMsg: chatMsgReducer, })
The combineReducers method is a higher-order reducer function. It accepts an object mapping specifies keys to a certain slice of the state managed by a specific reducer function and returns a new reducer function. If you run the following code, for instance:
console.log(JSON.stringify( reducer(initialState, { type: null }), null, 2, ))
You will see that the generated shape of the state looks like this:
{ "todoList": [], "chatMsg": [], }
We can try as well if our combined reducers are working and managing only the part of the state assigned to them. For instance:
console.log(JSON.stringify( reducer( initialState, { type: 'ADD_TODO', title: 'This is an example', completed: false, }, ), null, 2, ))
The output should display the generated state as the following:
{ "todoList": [ { "title": "This is an example", "completed": false, }, ], "chatMsg": [], }
This shows that each reducer is managing only the slice of the state assigned to them.