REACT useReducer

The useReducer hook is an advanced React hook used for managing complex state logic in a functional component. It is an alternative to useState and is particularly useful when the state depends on previous states or when multiple state transitions are required.

When to Use useReducer

  • When state logic is complex and involves multiple sub-values.
  • When the next state depends on the previous state.
  • When the logic for state updates is centralized and reusable (e.g., in a reducer function).
  • Useful in managing state for applications like form handling, complex toggles, or implementing state machines.

Syntax

const [state, dispatch] = useReducer(reducer, initialState);

Try It Now

  • reducer: A function that specifies how the state transitions happen. It takes the current state and an action, and returns the updated state.
  • initialState: The initial state value.
  • state: The current state managed by the useReducer hook.
  • dispatch: A function to trigger state transitions.

Reducer Function Structure

function reducer(state, action) {
    switch (action.type) {
        case "ACTION_TYPE_1":
            return { ...state, property: action.payload };
        case "ACTION_TYPE_2":
            return { ...state, anotherProperty: action.payload };
        default:
            return state; // Return the current state if no matching action is found.
    }
}

Try It Now

Basic Example: Counter

import React, { useReducer } from "react";

function Counter() {
    const initialState = { count: 0 };

    const reducer = (state, action) => {
        switch (action.type) {
            case "increment":
                return { count: state.count + 1 };
            case "decrement":
                return { count: state.count - 1 };
            case "reset":
                return { count: 0 };
            default:
                return state;
        }
    };

    const [state, dispatch] = useReducer(reducer, initialState);

    return (
        <div>
            <p>Count: {state.count}</p>
            <button onClick={() => dispatch({ type: "increment" })}>Increment</button>
            <button onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
            <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
        </div>
    );
}

export default Counter;

Try It Now

Example: Form Handling

import React, { useReducer } from "react";

const initialState = {
    username: "",
    email: "",
    password: "",
};

function reducer(state, action) {
    switch (action.type) {
        case "updateField":
            return { ...state, [action.field]: action.value };
        case "reset":
            return initialState;
        default:
            return state;
    }
}

function FormExample() {
    const [state, dispatch] = useReducer(reducer, initialState);

    const handleChange = (e) => {
        dispatch({
            type: "updateField",
            field: e.target.name,
            value: e.target.value,
        });
    };

    const handleReset = () => {
        dispatch({ type: "reset" });
    };

    return (
        <form>
            <input
                name="username"
                value={state.username}
                onChange={handleChange}
                placeholder="Username"
            />
            <input
                name="email"
                value={state.email}
                onChange={handleChange}
                placeholder="Email"
            />
            <input
                name="password"
                type="password"
                value={state.password}
                onChange={handleChange}
                placeholder="Password"
            />
            <button type="button" onClick={handleReset}>
                Reset
            </button>
            <p>Form State: {JSON.stringify(state)}</p>
        </form>
    );
}

export default FormExample;

Try It Now

Key Concepts of useReducer

  1. Centralized State Management: useReducer centralizes all state logic in the reducer function, making it easier to manage complex states.
  2. Action-Based Updates: State changes are triggered by dispatching an action. Actions have a type and often a payload.
  3. Immutability: Always return a new state object in the reducer. Do not directly modify the current state.

Example: Todo App

import React, { useReducer } from "react";

const initialState = [];

function reducer(state, action) {
    switch (action.type) {
        case "add":
            return [...state, { id: Date.now(), text: action.payload, completed: false }];
        case "toggle":
            return state.map((todo) =>
                todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
            );
        case "remove":
            return state.filter((todo) => todo.id !== action.payload);
        default:
            return state;
    }
}

function TodoApp() {
    const [todos, dispatch] = useReducer(reducer, initialState);
    const [text, setText] = React.useState("");

    const handleAdd = () => {
        if (text.trim()) {
            dispatch({ type: "add", payload: text });
            setText("");
        }
    };

    return (
        <div>
            <h3>Todo List</h3>
            <input
                type="text"
                value={text}
                onChange={(e) => setText(e.target.value)}
                placeholder="Add a todo"
            />
            <button onClick={handleAdd}>Add</button>
            <ul>
                {todos.map((todo) => (
                    <li key={todo.id} style={{ textDecoration: todo.completed ? "line-through" : "" }}>
                        <span onClick={() => dispatch({ type: "toggle", payload: todo.id })}>
                            {todo.text}
                        </span>
                        <button onClick={() => dispatch({ type: "remove", payload: todo.id })}>
                            Remove
                        </button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default TodoApp;

Try It Now

Key Points

  • Better for Complex State Logic: Prefer useReducer over useState when the state involves multiple transitions or dependencies.
  • Action Types Should Be Descriptive: Use clear and descriptive action types like add, remove, or toggle.
  • Avoid Overusing Reducers: If the state logic is simple, useState may be a better fit.

Common Mistakes

  1. Modifying State Directly: Always return a new state object in the reducer.
  2. Using Reducers for Simple Logic: If the state logic is straightforward, useState is simpler.
  3. Overcomplicating Actions: Keep actions minimal and focused.

Best Practices

  1. Use constants or enums for action types to avoid typos.
  2. Encapsulate complex logic within the reducer for better readability.
  3. Structure the state and actions in a scalable way for larger components or apps.

 

The useReducer hook is an essential tool in a React developer’s toolkit, especially for building scalable and maintainable state logic in functional components.