0% completed
The next fundamental part of the Redux architecture is the reducers. They function just like the reducers used in the useReducer
. By the end of this lesson, you will have learned about the functionality of the reducer in Redux state managment.
Reducers are pure functions that determine how the application's state changes in response to an action. The reducer takes the current state and an action as arguments, and it returns a new state based on the action type and payload.
Reducers should never mutate the state directly. Instead, they must return a new state object that reflects the updates. This immutability is essential in Redux because it helps keep the state changes predictable and traceable.
combineReducers
to handle different parts of the state.A reducer function typically has the following signature:
const reducer = (state, action) => { switch (action.type) { case 'ACTION_TYPE': // logic to update state return newState; default: return state; } };
Let's continue with the to-do app example and see how reducers work in this context.
The initial state of our to-do application is an empty list of to-dos. It looks like this:
const initialState = [];
The reducer will manage different actions like adding, removing, and toggling the completion status of to-do items. Let's create a reducer function to handle these actions.
const todosReducer = (state = initialState, action) => { switch (action.type) { case 'ADD_TODO': // Create a new to-do object and return the new state return [ ...state, // Keep existing to-dos { id: Date.now(), // Assign a unique id (could be any method) text: action.payload.text, completed: action.payload.completed } ]; case 'REMOVE_TODO': // Remove a to-do item by its id return state.filter(todo => todo.id !== action.payload.id); case 'TOGGLE_TODO': // Toggle the completion status of a to-do by its id return state.map(todo => todo.id === action.payload.id ? { ...todo, completed: !todo.completed } // Toggle completed status : todo ); default: // Return the current state if no action matches return state; } };
ADD_TODO
:
text
from the action's payload, sets its completed
status to false
, and assigns a unique id
using Date.now()
(though in a real-world app, you would use a more reliable method for generating IDs)....state
spread operator ensures that the existing to-dos are preserved while adding the new to-do.REMOVE_TODO
:
id
.filter()
method creates a new array excluding the to-do item with the specified id
.TOGGLE_TODO
:
completed
status of a to-do item.map()
method iterates over all to-dos and toggles the completed
field for the to-do that matches the provided id
.default
:
When an action is dispatched, Redux calls the reducer function, passing the current state and the action object as arguments. Based on the action's type, the reducer decides how to update the state.
For example:
If you dispatch an action to add a new to-do:
const addTodoAction = { type: 'ADD_TODO', payload: { text: 'Learn Redux', completed: false } };
The todosReducer
would handle this action by returning a new array that includes the new to-do.
If you dispatch an action to remove a to-do:
const removeTodoAction = { type: 'REMOVE_TODO', payload: { id: 1 } };
The reducer will filter out the to-do with id: 1
from the current state and return the updated state.
If you dispatch an action to toggle a to-do's completion:
const toggleTodoAction = { type: 'TOGGLE_TODO', payload: { id: 1 } };
The reducer will find the to-do with id: 1
and toggle its completed
status.
In larger applications, you might have multiple pieces of state to manage.
For example, you might have different states for managing user authentication, to-do lists, and settings. In such cases, you can combine multiple reducers into a single root reducer using Redux's combineReducers
function.
Example:
import { combineReducers } from 'redux'; import { todosReducer } from './todosReducer'; import { userReducer } from './userReducer'; const rootReducer = combineReducers({ todos: todosReducer, user: userReducer }); export default rootReducer;
In this case, the state would have two top-level properties: todos
and user
. The todosReducer
would handle the todos
part of the state, while userReducer
would handle the user
part of the state.
Here are some important notes to take about reducers:
Immutability: Always return a new state from the reducer rather than mutating the current state. Redux uses shallow comparison to detect state changes, so modifying the state directly will break Redux's ability to detect changes, leading to bugs.
Example of correct state update:
return [...state, newItem]; // State is not mutated
Example of incorrect state update:
state.push(newItem); // Mutates the original state (incorrect)
Pure Functions: Reducers should not have side effects (such as logging, network requests, etc.), and they should return the same output for the same input.
By using reducers and handling actions properly, Redux ensures predictable state management, especially in larger applications.
.....
.....
.....