What is 'combineReducers' ?
Divyansh Singh
Senior Software Engineer @ Razorpay | Tech Writer | Frontend | B.tech (CSE'22)
Introduction
In Redux, the state of the application is managed by a single object called the store. The store is updated by reducer functions, which take the current state and an action as input and return a new state based on the action.
As the application grows in complexity, it becomes necessary to split the reducer into smaller, more manageable functions, each responsible for updating a specific slice of the state. This is where the combineReducers function comes in.
The combineReducers function allows you to combine multiple reducer functions into a single function that can be passed to the Redux store. The resulting reducer function handles multiple slices of the state, each controlled by a separate reducer function.
Usage
The combineReducers function takes an object as its argument, where each key represents a slice of the state and each value represents a reducer function that controls that slice. The resulting function returned by combineReducers can be passed to the Redux store as the root reducer.
Here's an example:
import { combineReducers, createStore } from 'redux'
// REDUCER - 01
const todosReducer = (state = [], action) => {
? switch (action.type) {
? ? case 'ADD_TODO':
? ? ? return [...state, { text: action.text, completed: false }];
? ? case 'TOGGLE_TODO':
? ? ? return state.map((todo, index) =>
? ? ? ? action.index === index
? ? ? ? ? ? { ...todo, completed: !todo.completed }
? ? ? ? ? : todo
? ? ? );
? ? default:
? ? ? return state;
? }
};
// REDUCER - 02
const visibilityFilterReducer = (state = 'SHOW_ALL', action) => {
? switch (action.type) {
? ? case 'SET_VISIBILITY_FILTER':
? ? ? return action.filter;
? ? default:
? ? ? return state;
? }
};
// COMBINING REDUCERS
const rootReducer = combineReducers({
? todos: todosReducer,
? visibilityFilter: visibilityFilterReducer,
});
const store = createStore(rootReducer);;
In this example, we define two reducer functions: todosReducer and visibilityFilterReducer. todosReducer handles the state of the todos list, while visibilityFilterReducer handles the currently selected visibility filter.
We then pass these two reducer functions to combineReducers, along with an object that maps each reducer function to a corresponding key in the state object. The resulting rootReducer function is passed to the createStore function to create the Redux store.
When an action is dispatched to the store, the rootReducer function is invoked with the current state and the action object. The rootReducer function then invokes each individual reducer function, passing in the corresponding slice of the state and the action object. Each reducer function updates its own slice of the state and returns the new state object. The rootReducer function then combines all of the updated state slices into a new state object, which represents the updated state of the entire store.
Usage with Selectors
While each reducer is responsible for updating a specific slice of the state, it's often necessary to access state from other reducers in order to calculate a derived state or to dispatch a new action based on the current state. This is where selectors come in.
A selector is a function that extracts a specific slice of the state from the Redux store. Selectors can be defined in a separate file and imported into the reducer functions that need them.
领英推荐
Here's an example of a selector function:
export const getVisibleTodos = (todos, filter) =>
switch (filter) {
case "SHOW_ALL":
return todos;
case "SHOW_COMPLETED":
return todos.filter((todo) => todo.completed);
case "SHOW_ACTIVE":
return todos.filter((todo) => todo.completed === false);
default:
throw new Error(`Unknown filter: ${filter}`);
}
};
This function takes the `todos` array and the `filter` string as arguments and returns a filtered array of todos based on the selected visibility filter.
To use this selector in our `todosReducer`, we can import it and call it with the relevant arguments:
import { getVisibleTodos } from "./selectors"
const todosReducer = (state = [], action) => {
switch (action.type) {
case "ADD_TODO":
return [...state, { text: action.text, completed: false }];
case "TOGGLE_TODO":
return state.map((todo, index) =>
action.index === index ? { ...todo, completed: !todo.completed } : todo
);
case "SET_VISIBILITY_FILTER":
return {
...state,
visibilityFilter: action.filter,
};
default:
return state;
}
};
const rootReducer = combineReducers({
todos: (state, action) =>
todosReducer(getVisibleTodos(state.todos, state.visibilityFilter), action),
visibilityFilter: visibilityFilterReducer,
});;
In this example, we're using the getVisibleTodos selector function in the rootReducer to filter the todos array based on the currently selected visibility filter. The filtered todos array is then passed to the todosReducer function as the state argument.
Conclusion
The combineReducers function is a powerful tool for managing the state of a Redux application. By splitting the reducer into smaller functions, each responsible for a specific slice of the state, the code becomes more modular and easier to maintain.
While selectors aren't strictly necessary when using combineReducers, they can help to manage complex state and simplify the reducer functions. By extracting specific slices of the state and returning derived state, selectors make it easier to reason about the state of the application and ensure that each reducer function is focused on a specific slice of the state.
I hope this article has been helpful in understanding how combineReducers works in Redux. By combining this function with selectors and other Redux tools, you can build powerful and scalable applications with ease.