React Advanced

0% completed

Previous
Next
Working with Reducers in Redux

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.

What are Reducers in Redux?

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.

Key Concepts About Reducers:

  1. Reducers are pure functions: They don't modify the state or cause side effects. They simply calculate the next state based on the current state and the action.
  2. Reducers are responsible for handling actions: When an action is dispatched, it is processed by the relevant reducer(s) to update the state.
  3. Reducers return a new state object: The reducer must return a new version of the state, not modify the existing state directly. This is typically done using spread operators or other immutability techniques.
  4. Reducers are combined for larger apps: In more complex applications, multiple reducers are used, and they are combined using combineReducers to handle different parts of the state.

Basic Syntax of a Reducer:

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.

Initial State:

The initial state of our to-do application is an empty list of to-dos. It looks like this:

const initialState = [];

Reducer Function:

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; } };

Explanation:

  1. ADD_TODO:

    • This action is triggered when you want to add a new to-do item to the list.
    • It creates a new to-do object with the 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).
    • The ...state spread operator ensures that the existing to-dos are preserved while adding the new to-do.
  2. REMOVE_TODO:

    • This action is used to remove a to-do item by its id.
    • The filter() method creates a new array excluding the to-do item with the specified id.
  3. TOGGLE_TODO:

    • This action toggles the completed status of a to-do item.
    • The map() method iterates over all to-dos and toggles the completed field for the to-do that matches the provided id.
  4. default:

    • If no action matches, the reducer simply returns the current state. This ensures that the state remains unchanged if the action type is not recognized.

How the Reducer Works

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.

Combining Reducers

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.

Important Notes on Reducers

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.

.....

.....

.....

Like the course? Get enrolled and start learning!
Previous
Next