React useReducer Hook Explained with Real Examples and Best Practices bilal shafqat

React useReducer Hook Explained with Real Examples and Best Practices (2025 Guide)

Confused about when to use useReducer in React? Learn how this powerful hook helps manage complex state logic, with real examples and best practices for 2025.

Introduction

Managing state in React is easy — until it’s not. When your component’s state logic becomes complex, using multiple useState calls can quickly get messy and hard to maintain.

This is where useReducer comes in. It gives you a structured way to manage state transitions, especially when those transitions depend on the previous state.

In this guide, you’ll learn:

  • What useReducer is
  • When to use it vs useState
  • How it compares to Redux
  • How to structure reducers
  • Real-world examples and best practices

What is useReducer?

useReducer is a React hook that’s an alternative to useState for managing more complex state.

It works similarly to Redux by using a reducer function and a dispatch method:

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

This hook returns the current state and a dispatch function you can use to trigger state updates based on actions.

Why Use useReducer Instead of useState?

Why Use useReducer Instead of useState? bilal shafqat

useReducer shines in components where:

  • There are multiple pieces of state
  • State updates are conditional or depend on previous state
  • You want to centralize state logic in one function

useState is great for simple values like booleans, strings, and numbers — but when your state logic grows in complexity, useReducer helps keep it maintainable.

Basic Syntax of useReducer

Basic Syntax of useReducer bilal shafqat

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      return state;
  }
}

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

Usage:

<button onClick={() => dispatch({ type: "increment" })}>
  {state.count}
</button>

useReducer vs useState

Feature useState useReducer
Ease of use ✅ Simple ⚠️ Slightly verbose
Best for Simple state Complex or conditional logic
State separation Multiple variables Single object
Predictability Less structured Centralized logic

Example: Toggle & Counter Together

const initialState = { count: 0, show: true };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { ...state, count: state.count + 1 };
    case "toggle":
      return { ...state, show: !state.show };
    default:
      return state;
  }
}

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

Now both count and show are controlled in one predictable place.

useReducer + useContext for Global State

Want to manage shared state across multiple components without Redux?

Use useReducer inside a context provider:

const AuthContext = createContext();

function AuthProvider({ children }) {
  const [state, dispatch] = useReducer(authReducer, { isAuthenticated: false });

  return (
    <AuthContext.Provider value={{ state, dispatch }}>
      {children}
    </AuthContext.Provider>
  );
}

Common Mistakes to Avoid

1. Returning the old state instead of a new copy

// ❌ Mutating state
state.count++;

// ✅ Correct
return { ...state, count: state.count + 1 };

2. Handling too many unrelated things in one reducer

Split reducers or use multiple reducers with combineReducers (in Redux) or custom logic in React.

3. Using it when useState would be simpler

If you just need to toggle a boolean or store a single input, stick with useState().

When to Use useReducer (Best Scenarios)

  • Complex forms with validation logic
  • Multi-step workflows (wizards, tabs)
  • State objects with multiple sub-properties
  • Redux-like global stores without external libraries
  • Reusable UI components with configurable state behavior

Best Practices for useReducer (2025)

  • Always return new state objects (avoid mutation)
  • Use constants for action types to reduce typos
  • Group related state into one reducer
  • Combine with useContext for lightweight global state
  • Use action creators for better readability

Final Thoughts

useReducer() gives you better structure and flexibility than useState() when state logic becomes complex or when you need shared logic across multiple components.

Use it when:

  • You have multiple values that change together
  • Your state transitions are based on conditions
  • You want to extract state logic for readability

Don’t replace useState in every case — but when you need it, useReducer makes your code scalable and maintainable.

Leave A Comment