useReducer Hook

The useReducer Hook is a powerful alternative to useState for managing state in complex React applications. It is particularly useful when state logic involves multiple sub-values or when the next state depends on the previous one. Here's a detailed explanation of how it works, its benefits, and how to implement it.

Overview

The useReducer Hook provides a way to manage state using a reducer function, which takes the current state and an action object to determine the next state. It is similar to Redux in concept but is used locally within a React component.

Syntax

const [state, dispatch] = useReducer(reducer, initialState);
  • reducer: A function that receives the current state and an action object, and returns the new state.

  • initialState: The initial value of the state.

Parameters

  • state: The current state managed by the reducer.

  • dispatch: A function to send actions to the reducer.

Example 1: Simple Counter

This example demonstrates a simple counter application that increments and decrements a value.

import { useReducer } from "react";

const ACTIONS = {
    INCREMENT: "increment",
    DECREMENT: "decrement",
};

function reducer(state, action) {
    switch (action.type) {
        case ACTIONS.INCREMENT:
            return { count: state.count + 1 };
        case ACTIONS.DECREMENT:
            return { count: state.count - 1 };
        default:
            return state;
    }
}

export default function App() {
    const [state, dispatch] = useReducer(reducer, { count: 0 });

    function increment() {
        dispatch({ type: ACTIONS.INCREMENT });
    }

    function decrement() {
        dispatch({ type: ACTIONS.DECREMENT });
    }

    return (
        <>
            <button onClick={increment}>+</button>
            <span>{state.count}</span>
            <button onClick={decrement}>-</button>
        </>
    );
}

Explanation

  • ACTIONS: An object that defines the types of actions that can be dispatched.

  • reducer: Handles state transitions based on the action received.

  • dispatch: Triggers the reducer with a specific action type.

Example 2: Todo List Application

This more advanced example shows how to manage a list of todo items using useReducer.

import { useState, useReducer } from "react";
import Todo from "./Todo";

export const ACTIONS = {
    ADD_TODO: "add-todo",
    TOGGLE_TODO: "toggle-todo",
    DELETE_TODO: "delete-todo",
};

function reducer(todos, action) {
    switch (action.type) {
        case ACTIONS.ADD_TODO:
            return [...todos, newTodo(action.payload.name)];
        case ACTIONS.TOGGLE_TODO:
            return todos.map((todo) => {
                if (todo.id === action.payload.id) {
                    return { ...todo, complete: !todo.complete };
                }
                return todo;
            });
        case ACTIONS.DELETE_TODO:
            return todos.filter((todo) => todo.id !== action.payload.id);
        default:
            return todos;
    }
}

function newTodo(name) {
    return {
        id: Date.now(),
        name: name,
        complete: false,
    };
}

export default function App() {
    const [todos, dispatch] = useReducer(reducer, []);
    const [name, setName] = useState("");

    function handleSubmit(e) {
        e.preventDefault();
        dispatch({ type: ACTIONS.ADD_TODO, payload: { name } });
        setName("");
    }

    return (
        <>
            <form onSubmit={handleSubmit}>
                <input
                    type="text"
                    value={name}
                    onChange={(e) => setName(e.target.value)}
                />
            </form>
            {todos.map((todo) => (
                <Todo key={todo.id} todo={todo} dispatch={dispatch} />
            ))}
        </>
    );
}

Explanation

  • reducer: Manages the state of the todo list based on the dispatched actions.

  • newTodo: Creates a new todo item with a unique ID and default complete status.

  • dispatch: Triggers state changes in the todo list by adding, toggling, or deleting items.

Benefits of useReducer

  1. Centralized State Management: useReducer centralizes the logic for handling state updates in one place (the reducer function), making it easier to manage complex state changes.

  2. Predictable State Updates: By using a reducer function, state transitions are predictable and controlled, reducing the risk of unexpected behavior.

  3. Optimized Performance: useReducer is particularly useful in optimizing components that have complex state logic or where frequent state changes are needed.

When to Use useReducer

  • Complex State Logic: When your component has complex state logic with multiple sub-values or actions, useReducer provides a clear and structured approach.

  • Related State Transitions: When multiple state transitions share logic, useReducer consolidates this logic into a single function, making it easier to manage and maintain.

  • Performance Considerations: useReducer is more performant in situations where frequent re-renders are needed, and state changes can be optimized by batching updates.

Additional Resources

Was this helpful?