Lesson 33 of 60 – useReducer Hook
55%

useReducer Hook

The useReducer Hook is used to manage state in React, especially when state logic is more complex than a simple useState update. It works with a reducer function and actions.

Note: The useReducer Hook is useful when multiple state updates are related or when the state transition logic needs to be organized in one place.

1. What is useReducer?

useReducer is a React Hook used for managing state through a reducer function.

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

It returns the current state and a dispatch function.

2. Importing useReducer

Before using useReducer, import it from React.

import { useReducer } from "react";

You can then use the Hook inside a functional component.

3. Basic useReducer Syntax

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

There are three important parts:

  • state – the current state
  • dispatch – sends an action
  • reducer – decides how the state changes

4. What is a Reducer?

A reducer is a function that receives the current state and an action and returns the next state.

function reducer(state, action) {

    return newState;

}

The reducer contains the logic used to update the state.

5. What is an Action?

An action describes what happened or what operation should be performed.

{
    type: "increment"
}

The reducer checks the action type and decides what state should be returned.

6. What is dispatch?

The dispatch function sends an action to the reducer.

dispatch({
    type: "increment"
});

React passes the action to the reducer, which calculates the next state.

7. Basic Counter Example

import { useReducer } from "react";

function reducer(state, action) {

    if (action.type === "increment") {
        return state + 1;
    }

    return state;
}

function Counter() {

    const [count, dispatch] =
        useReducer(reducer, 0);

    return (
        <div>

            <h2>
                Count: {count}
            </h2>

            <button
                onClick={() =>
                    dispatch({
                        type: "increment"
                    })
                }
            >
                Increase
            </button>

        </div>
    );
}

export default Counter;

8. Initial State

The second argument of useReducer is the initial state.

const initialState = 0;

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

The initial state is used when the component starts.

9. Reducer with switch

A switch statement is commonly used to handle different action types.

function reducer(state, action) {

    switch (action.type) {

        case "increment":
            return state + 1;

        case "decrement":
            return state - 1;

        default:
            return state;
    }
}

10. Increment and Decrement

function reducer(state, action) {

    switch (action.type) {

        case "increment":
            return state + 1;

        case "decrement":
            return state - 1;

        default:
            return state;
    }
}

Different actions can perform different state transitions.

11. Dispatching Different Actions

<button
    onClick={() =>
        dispatch({
            type: "increment"
        })
    }
>
    +
</button>

<button
    onClick={() =>
        dispatch({
            type: "decrement"
        })
    }
>
    -
</button>

Each button sends a different action to the reducer.

12. Reset Action

A reducer can also handle a reset operation.

function reducer(state, action) {

    switch (action.type) {

        case "increment":
            return state + 1;

        case "decrement":
            return state - 1;

        case "reset":
            return 0;

        default:
            return state;
    }
}

13. Passing Data with an Action

An action can contain additional data, usually inside a payload property.

dispatch({
    type: "add",
    payload: 5
});

The reducer can use this value to calculate the next state.

14. Using Payload in Reducer

function reducer(state, action) {

    switch (action.type) {

        case "add":
            return state + action.payload;

        default:
            return state;
    }
}

Here, action.payload contains the amount to add.

15. useReducer with Object State

useReducer is especially useful when the state contains multiple related values.

const initialState = {
    name: "",
    age: 0,
    email: ""
};

The reducer can update individual properties based on actions.

16. Updating Object State

function reducer(state, action) {

    switch (action.type) {

        case "setName":

            return {
                ...state,
                name: action.payload
            };

        default:
            return state;
    }
}

The spread operator keeps the other state properties unchanged.

17. Multiple Actions

function reducer(state, action) {

    switch (action.type) {

        case "setName":
            return {
                ...state,
                name: action.payload
            };

        case "setAge":
            return {
                ...state,
                age: action.payload
            };

        case "setEmail":
            return {
                ...state,
                email: action.payload
            };

        default:
            return state;
    }
}

18. useReducer for Forms

A reducer can manage multiple form fields from one state object.

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

Actions can update each field in a predictable way.

19. Form Input with dispatch

<input
    value={state.username}
    onChange={(e) =>
        dispatch({
            type: "username",
            payload: e.target.value
        })
    }
/>

The input value is sent to the reducer through an action.

20. useReducer vs useState

useState useReducer
Simple state updates. More structured state logic.
Uses a setter function. Uses a reducer and dispatch.
Good for simple values. Useful for related or complex state transitions.
Easy for small components. Useful when many actions affect the same state.

21. When Should You Use useReducer?

useReducer can be useful when:

  • State has multiple related values.
  • Many actions can change the same state.
  • State transition logic is becoming complicated.
  • You want state update logic in one reducer function.
  • A form contains many related fields.
  • Different user actions produce different state changes.

22. Reducer Should Be Predictable

A reducer should calculate the next state from the current state and the action.

function reducer(state, action) {

    switch (action.type) {

        case "increment":
            return state + 1;

        default:
            return state;
    }
}

The reducer should not perform unrelated side effects.

23. Do Not Mutate State Directly

State should be updated by returning a new state instead of directly mutating the existing state object.

Avoid:

state.name = "Rahul";
return state;

Prefer:

return {
    ...state,
    name: "Rahul"
};

24. Complete Counter with useReducer

import { useReducer } from "react";

const initialState = {
    count: 0
};

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

function Counter() {

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

    return (
        <div>

            <h2>
                Count: {state.count}
            </h2>

            <button
                onClick={() =>
                    dispatch({
                        type: "increment"
                    })
                }
            >
                Increase
            </button>

            <button
                onClick={() =>
                    dispatch({
                        type: "decrement"
                    })
                }
            >
                Decrease
            </button>

            <button
                onClick={() =>
                    dispatch({
                        type: "reset"
                    })
                }
            >
                Reset
            </button>

        </div>
    );
}

export default Counter;

25. Reducer with Multiple State Values

const initialState = {
    count: 0,
    message: "Ready"
};

function reducer(state, action) {

    switch (action.type) {

        case "increment":

            return {
                count: state.count + 1,
                message: "Count increased"
            };

        case "decrement":

            return {
                count: state.count - 1,
                message: "Count decreased"
            };

        default:
            return state;
    }
}

One reducer can manage several related values together.

26. Combining useReducer with Context

useReducer can be combined with Context when application state needs to be shared by many components.

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

<AppContext.Provider
    value={{
        state,
        dispatch
    }}
>

    <App />

</AppContext.Provider>

This pattern is useful for organizing shared application state.

27. Action Types

Action types should clearly describe the operation being performed.

"increment"
"decrement"
"reset"
"setName"
"setEmail"
"addItem"
"removeItem"

Clear action names make reducer logic easier to understand.

28. Common useReducer Mistakes

  • Forgetting to import useReducer.
  • Forgetting to return a new state.
  • Mutating state directly.
  • Using incorrect action types.
  • Forgetting the default case in a switch statement.
  • Sending the wrong payload.
  • Putting unrelated side effects inside the reducer.
  • Using useReducer when simple useState logic would be enough.

29. useReducer Workflow

The basic workflow of useReducer is:

  1. Create the initial state.
  2. Create the reducer function.
  3. Call useReducer().
  4. Get the state and dispatch function.
  5. Dispatch an action when an event occurs.
  6. The reducer receives the state and action.
  7. The reducer returns the new state.
  8. React renders the component with the updated state.
User Action
     ↓
  dispatch()
     ↓
   Action
     ↓
  Reducer
     ↓
 New State
     ↓
 Re-render

30. useReducer Best Practices

  • Use useReducer for complex state logic.
  • Keep reducers focused and predictable.
  • Use meaningful action types.
  • Keep related state values together when appropriate.
  • Use payloads for dynamic action data.
  • Do not mutate state directly.
  • Always return the appropriate next state.
  • Include a default case in reducer logic.
  • Keep side effects outside the reducer.
  • Combine useReducer with Context when shared state is needed across many components.

📌 Key Points

  • useReducer is a React Hook for managing state with a reducer function.
  • A reducer receives the current state and an action.
  • dispatch() sends an action to the reducer.
  • The reducer returns the next state.
  • Actions usually contain a type property.
  • Actions can also contain a payload.
  • useReducer is useful for complex or related state updates.
  • It is commonly used with forms and multiple state values.
  • State should not be mutated directly.
  • useReducer can be combined with Context for shared application state.

🧠 Quick Quiz

Question: What does the dispatch function do in useReducer?