Popular Searches
Popular Course Categories
Popular Courses

useReducer Hook

useReducer Hook

Advanced React Hooks

useReducer Hook in React JS

The useReducer Hook is a built-in React Hook used to manage component state through a reducer function. It is especially useful when state management involves multiple related values, complex update logic, or several actions that modify the same state. Instead of writing all update logic inside event handlers, useReducer organizes it into a single reducer function.

Course: JustAcademy React JS Training Course

Course Demo: Register for React JS Course Demo


1. What Is the useReducer Hook?

useReducer is a React Hook that allows a component to manage state using a reducer function. The reducer receives the current state and an action, then returns the next state.

It is an alternative to useState and is useful when several state updates are related or when the update logic becomes difficult to manage.

Basic Syntax

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

Explanation:

  • state – the current state value.
  • dispatch – a function used to send an action to the reducer.
  • reducer – a function that calculates the next state.
  • initialState – the initial state value.

2. Why Do We Use useReducer?

In simple components, useState is usually sufficient. When multiple actions update related state values, useReducer can make the code easier to organize and understand.

  • It centralizes state update logic in one reducer function.
  • It makes complex state transitions easier to manage.
  • It supports multiple actions, such as adding, editing, deleting, and resetting data.
  • It separates state update logic from event handlers.
  • It can make testing state transitions easier.
  • It works well with objects, arrays, forms, shopping carts, and task lists.

3. How Does useReducer Work?

The useReducer Hook follows a simple process:

  1. The component displays the current state.
  2. A user performs an action, such as clicking a button.
  3. The component calls dispatch(action).
  4. React passes the current state and action to the reducer.
  5. The reducer returns the next state.
  6. React renders the component using the updated state.

Flow Diagram

User Interaction
       |
       v
dispatch(action)
       |
       v
Reducer(state, action)
       |
       v
Return New State
       |
       v
React Re-renders Component
       |
       v
Updated UI

4. First Example: Counter Using useReducer

A counter is a simple way to understand how useReducer works.

Complete Code

import { useReducer } from "react";
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    case "reset":
      return 0;
    default:
      throw new Error("Unknown action: " + action.type);
  }
}
function Counter() {
  const [count, dispatch] = useReducer(reducer, 0);
  return (
    <div>
      <h2>Count: {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;

Code Explanation

  • useReducer(reducer, 0) initializes the counter with a value of zero.
  • The reducer checks action.type to determine the requested operation.
  • dispatch({ type: "increment" }) requests an increase.
  • dispatch({ type: "decrement" }) requests a decrease.
  • dispatch({ type: "reset" }) resets the counter to zero.
  • The reducer returns the next state, and React updates the UI.

5. Understanding the Reducer Function

A reducer is a JavaScript function that receives two parameters: the current state and an action. It returns the next state based on that action.

Syntax

function reducer(state, action) {
  switch (action.type) {
    case "ACTION_NAME":
      return newState;
    default:
      return state;
  }
}

Parameters:

  • state – the current state before the action is processed.
  • action – an object describing what should happen.

Important: A reducer must be pure. It should calculate and return the next state without modifying the existing state, making API requests, or performing other side effects.


6. Understanding Actions and Dispatch

An action describes the change that should happen to the state. By convention, an action is an object with a type property and may contain additional data.

Example Actions

{ type: "increment" }
{ type: "decrement" }
{ type: "addTodo", text: "Learn React" }
{ type: "updateName", name: "Rahul" }
{ type: "deleteTodo", id: 3 }

The dispatch function sends an action to the reducer.

dispatch({ type: "increment" });
dispatch({ type: "updateName", name: "Rahul" });

React processes the action through the reducer and uses the returned state for the next render. Calling dispatch does not immediately change the state variable inside the currently executing code.


7. useReducer with Object State

useReducer is useful when a component manages multiple related values, such as a person's name and age.

Complete Example

import { useReducer } from "react";
const initialState = {
  name: "Amit",
  age: 25
};
function reducer(state, action) {
  switch (action.type) {
    case "changeName":
      return {
        ...state,
        name: action.name
      };
    case "increaseAge":
      return {
        ...state,
        age: state.age + 1
      };
    case "reset":
      return initialState;
    default:
      throw new Error("Unknown action: " + action.type);
  }
}
function UserProfile() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <h2>User Profile</h2>
      <p>Name: {state.name}</p>
      <p>Age: {state.age}</p>
      <button onClick={() => dispatch({ type: "changeName", name: "Rahul" })}>
        Change Name
      </button>
      <button onClick={() => dispatch({ type: "increaseAge" })}>
        Increase Age
      </button>
      <button onClick={() => dispatch({ type: "reset" })}>
        Reset
      </button>
    </div>
  );
}
export default UserProfile;

Explanation: The state contains both name and age. The spread operator (...state) preserves the other state properties when one property changes.


8. useReducer with Array State

You can use useReducer to manage arrays, such as a list of tasks. Each action can add or remove an item without directly mutating the existing array.

Complete Example

import { useReducer } from "react";
const initialTasks = [
  { id: 1, text: "Learn React", done: false }
];
function taskReducer(state, action) {
  switch (action.type) {
    case "add":
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          done: false
        }
      ];
    case "toggle":
      return state.map(task =>
        task.id === action.id
          ? { ...task, done: !task.done }
          : task
      );
    case "delete":
      return state.filter(task => task.id !== action.id);
    default:
      throw new Error("Unknown action: " + action.type);
  }
}
function TaskList() {
  const [tasks, dispatch] = useReducer(taskReducer, initialTasks);
  return (
    <div>
      <h2>Task List</h2>
      {tasks.map(task => (
        <div key={task.id}>
          <span>
            {task.text} - {task.done ? "Completed" : "Pending"}
          </span>
          <button onClick={() => dispatch({ type: "toggle", id: task.id })}>
            Toggle
          </button>
          <button onClick={() => dispatch({ type: "delete", id: task.id })}>
            Delete
          </button>
        </div>
      ))}
      <button onClick={() => dispatch({
        type: "add",
        id: Date.now(),
        text: "Practice React"
      })}>
        Add Task
      </button>
    </div>
  );
}
export default TaskList;

Explanation:

  • add returns a new array with an additional task.
  • toggle creates a new object for the selected task.
  • delete returns a filtered array without the selected task.
  • The original state array and its objects are not mutated.

Note: Date.now() is used here to create a simple example ID in the event handler. For production applications, use a reliable unique-ID strategy appropriate to your data.


9. useReducer for Form Handling

When a form has multiple fields, validation states, or several actions, useReducer can organize form updates in one place.

Example

import { useReducer } from "react";
const initialForm = {
  name: "",
  email: ""
};
function formReducer(state, action) {
  switch (action.type) {
    case "change":
      return {
        ...state,
        [action.field]: action.value
      };
    case "reset":
      return initialForm;
    default:
      throw new Error("Unknown action: " + action.type);
  }
}
function RegistrationForm() {
  const [form, dispatch] = useReducer(formReducer, initialForm);
  function handleChange(event) {
    dispatch({
      type: "change",
      field: event.target.name,
      value: event.target.value
    });
  }
  return (
    <form onSubmit={event => event.preventDefault()}>
      <input
        name="name"
        value={form.name}
        onChange={handleChange}
        placeholder="Enter your name"
      />
      <input
        name="email"
        type="email"
        value={form.email}
        onChange={handleChange}
        placeholder="Enter your email"
      />
      <button type="button" onClick={() => dispatch({ type: "reset" })}>
        Reset Form
      </button>
      <p>Name: {form.name}</p>
      <p>Email: {form.email}</p>
    </form>
  );
}
export default RegistrationForm;

Explanation: The same change action updates either input based on the field name. The reset action restores the initial form values.


10. useReducer vs. useState

Both Hooks manage state, but they organize updates differently.

Feature useState useReducer
State updates Uses a setter function. Uses a dispatch function and reducer.
Best suited for Simple or independent state values. Complex or related state transitions.
Update logic Often written near event handlers. Centralized in the reducer function.
Actions No action objects are required. Actions commonly describe the requested change.
Arrays and objects Supported with immutable updates. Supported with immutable updates.
Code structure Usually simpler for small components. Can be clearer when many actions affect the same state.
Performance Often sufficient for simple state. Not automatically faster; use it for clearer state logic.

Example of when to choose: Use useState for a simple toggle or input. Consider useReducer for a shopping cart, multi-step form, task manager, or state with several related transitions.


11. Using the Initializer Function

useReducer accepts an optional third argument called init. It allows you to calculate the initial state from the second argument. This can be helpful when the initial state requires a more expensive calculation.

Syntax

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

Example

import { useReducer } from "react";
function createInitialState(initialCount) {
  return { count: initialCount };
}
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { ...state, count: state.count + 1 };
    default:
      throw new Error("Unknown action: " + action.type);
  }
}
function Counter() {
  const [state, dispatch] = useReducer(
    reducer,
    10,
    createInitialState
  );
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>
        Increase
      </button>
    </div>
  );
}
export default Counter;

Explanation: React calls createInitialState(10) to calculate the initial state. The initializer is not used to reset the state on every render.


12. Common useReducer Mistakes

Mistake Problem Correct Approach
Mutating state directly React may not detect the state change correctly. Return a new object or array.
Forgetting to return state The next state may become undefined. Return a valid next state from every supported action.
Unknown action type The reducer may silently return an invalid value if no default is handled. Handle supported actions and consider throwing for unknown actions.
Incorrect action payload The reducer may receive missing or invalid data. Pass the required information with the action.
Calling dispatch during render May cause repeated rendering or an infinite loop. Dispatch in event handlers or appropriate effects.
Putting side effects in the reducer Breaks reducer purity and can cause repeated side effects. Keep reducers pure and handle side effects elsewhere.
Forgetting the spread operator Other properties may be lost when returning an object. Use ...state when preserving existing properties.

13. Rules and Best Practices

  • Call useReducer only at the top level of a React function component or custom Hook.
  • Keep the reducer function pure and predictable.
  • Do not mutate objects or arrays in the existing state.
  • Return a valid next state for every supported action.
  • Use clear action names such as add, update, delete, and reset.
  • Pass only the information needed by the reducer in each action.
  • Keep API requests and other side effects outside the reducer.
  • Use useState when it provides a simpler solution.
  • Use the optional initializer when initial-state calculation is expensive or requires transformation.
  • Test the reducer independently by providing sample states and actions.

Strict Mode note: React may call reducer and initializer functions twice in development to help reveal accidental impurities. Pure reducers should produce the same result for the same state and action, without modifying existing state.


14. Practical Project: Shopping Cart with useReducer

A shopping cart is a useful project for practicing multiple actions and object-based state management.

Project Requirements

  • Display a list of products.
  • Add a product to the cart.
  • Remove a product from the cart.
  • Calculate the total number of cart items.
  • Calculate the total price.

Example Reducer

function cartReducer(state, action) {
  switch (action.type) {
    case "add":
      return [...state, action.product];
    case "remove":
      return state.filter(item => item.id !== action.id);
    case "clear":
      return [];
    default:
      throw new Error("Unknown action: " + action.type);
  }
}

Example Usage

const [cart, dispatch] = useReducer(cartReducer, []);
const addProduct = product => {
  dispatch({ type: "add", product });
};
const removeProduct = id => {
  dispatch({ type: "remove", id });
};
const clearCart = () => {
  dispatch({ type: "clear" });
};
const totalPrice = cart.reduce(
  (total, item) => total + item.price,
  0
);

Learning outcome: This project demonstrates how to organize actions, update arrays immutably, and calculate derived values from reducer-managed state. A complete shopping cart can also include quantity updates, duplicate-product handling, discounts, and checkout validation.


15. Interview Questions and Answers

Q1. What is useReducer in React?

useReducer is a React Hook that manages state through a reducer function that receives the current state and an action and returns the next state.

Q2. What does useReducer return?

It returns an array containing the current state and a dispatch function.

Q3. What is a reducer function?

A reducer is a pure function that calculates the next state based on the current state and the action received.

Q4. What is dispatch in useReducer?

dispatch sends an action to the reducer so React can calculate and render the next state.

Q5. What is an action in useReducer?

An action describes a requested state change. It commonly contains a type property and any additional information required by the reducer.

Q6. What is the difference between useState and useReducer?

useState updates state through a setter function, while useReducer centralizes update logic in a reducer and uses actions to describe changes.

Q7. Can useReducer manage arrays and objects?

Yes. It can manage primitive values, objects, arrays, and other state structures, provided updates are handled correctly.

Q8. Why should a reducer be pure?

A pure reducer produces the same next state for the same inputs and does not mutate existing state or perform side effects. This makes state updates predictable and easier to test.

Q9. What is the purpose of the third argument in useReducer?

The optional third argument is an initializer function that calculates the initial state from the second argument.

Q10. Can useReducer replace Redux?

useReducer can manage local component state and can be combined with Context for shared state. Redux is a separate state-management library that offers additional patterns and tooling for larger applications.

Q11. What happens if a reducer mutates the existing state?

React may not detect the change correctly, and the application can behave unpredictably. Reducers should return new objects or arrays instead of modifying existing state.

Q12. When should you use useReducer?

Use it when state transitions are complex, several actions update related values, or keeping update logic in one reducer makes the component easier to understand.


16. Practice Exercises

  1. Create a counter with increment, decrement, and reset actions.
  2. Create a user profile with name and age fields.
  3. Build a task manager with add, toggle, and delete actions.
  4. Use useReducer to manage a registration form.
  5. Create a shopping cart with add and remove actions.
  6. Write an initializer function for a counter with a configurable starting value.
  7. Identify and correct a reducer that mutates an array.
  8. Write unit tests for a reducer using sample state and actions.

17. Quick Revision Checklist

  • Understand the purpose and syntax of useReducer.
  • Know the roles of state, reducer, action, and dispatch.
  • Understand how actions produce state transitions.
  • Use switch statements or equivalent logic to handle actions.
  • Return new objects and arrays instead of mutating state.
  • Keep reducers pure and free of side effects.
  • Know when to choose useState or useReducer.
  • Understand the optional initializer function.
  • Practice counters, forms, task lists, and shopping carts.

18. Conclusion

The useReducer Hook provides a structured way to manage React state through actions and a reducer function. It is particularly useful when several related state changes need to be handled consistently. By keeping reducers pure, returning new state objects, and using clear action types, developers can build more predictable and maintainable React applications.

Official React Documentation: React useReducer Hook | Extracting State Logic into a Reducer

Explore the Course: JustAcademy React JS Training Course | Register for React JS Course Demo

whatsapp