React useReducer Hook: Manage Complex State Like a Pro

Last updated 2 months, 2 weeks ago | 84 views 75     5

Tags:- React

Introduction: Why Use useReducer in React?

Managing state in React often starts with useState, and for simple counters or toggles, that’s enough. But what if you’re dealing with more complex state logic—like forms, nested objects, or actions with multiple state transitions?

This is where the useReducer Hook shines. Inspired by Redux-style reducers, useReducer allows you to manage complex state updates in a predictable and centralized way.

Use it when:

  • Multiple pieces of state are interrelated.

  • Next state depends on previous state.

  • You want clearer state transitions for maintainability.


Understanding useReducer: Concept and Syntax

Basic Syntax

const [state, dispatch] = useReducer(reducer, initialState);
  • reducer: A function that handles state transitions.

  • initialState: The starting value of your state.

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


Reducer Function Pattern

A reducer takes the current state and an action, then returns a new state.

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

Step-by-Step: Using useReducer in React

1. Define Initial State

const initialState = { count: 0 };

2. Create a Reducer Function

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return initialState;
    default:
      throw new Error('Unknown action type');
  }
}

3. Initialize useReducer

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

4. Dispatch Actions

<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>

Complete Functional Example: Counter with useReducer

import React, { useReducer } from 'react';

// 1. Initial state
const initialState = { count: 0 };

// 2. Reducer function
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return initialState;
    default:
      return state;
  }
}

function Counter() {
  // 3. Hook setup
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div style={{ textAlign: 'center' }}>
      <h2>Count: {state.count}</h2>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

export default Counter;

Comparison Table: useState vs useReducer

Feature useState useReducer
Best for Simple state Complex, interrelated state
State update logic location Inline in component Centralized in reducer function
Suitable for multiple actions ❌ No ✅ Yes
Reducer-like structure ❌ No ✅ Yes
Ease of testing Medium High (pure function logic)

Tips & Common Pitfalls

Best Practices

  • Group related state in one reducer instead of multiple useState calls.

  • Use action objects to describe state changes clearly.

  • Keep your reducer pure—don’t include side effects like API calls.

⚠️ Common Mistakes

  • Mutating state directly in reducer—always return a new object.

  • Forgetting default case in switch—can break your app silently.

  • Over-engineering: For simple cases, stick with useState.


When Should You Use useReducer?

Use Case Recommended Approach
Counter or toggle logic useState
Multi-step form or wizard useReducer
Complex UI interaction logic useReducer
State with many transitions useReducer
Global app-wide state Context + useReducer or Redux

Conclusion: Mastering useReducer for Better State Management

The useReducer Hook is a powerful alternative to useState when your component's state logic becomes complex or hard to follow. It helps you:

  • Keep state transitions predictable

  • Centralize update logic

  • Improve maintainability as your app scales

Pro Tip: Combine useReducer with useContext for lightweight global state management—no Redux required!


Actionable Takeaways

  • Use useReducer for structured and testable state logic.

  • Always return a new state—never mutate!

  • Simplify your reducer with helper functions if needed.

  • Combine with useContext to manage global or shared state effectively.