useReducer

Learn when useReducer is a better fit than useState for React screens with complex transitions, event-driven state, and safer update logic.

Last reviewed: June 2026

·React 16.8+React 18React 19
✅
Quick Understanding: useReducer moves state updates into a reducer function so transitions are explicit and easier to reason about. It is especially useful when multiple events can change related state together.

🔍 What Is It?

useReducer is a React hook for state that changes through named actions instead of ad hoc setter calls.

  • →A reducer receives the current state and an action
  • →It returns the next state without mutating the previous one
  • →The component dispatches actions such as 'submitted' or 'reset'
  • →This makes complex UI behavior easier to test and debug

🎯 Why Do We Need It?

useState works well for small local values, but it becomes harder to follow when one screen has loading, success, error, editing, undo, or multistep transitions. useReducer helps teams model those transitions deliberately instead of scattering update logic across event handlers and effects.

🏗️ What Happens in Real Projects?

Senior React teams use useReducer for checkout flows, filters with many dependent controls, optimistic local workflows, modal state machines, and complex forms where several fields or statuses must change together.

⚙️ How It Works

You define an initial state object and a reducer. The reducer handles a fixed set of action types. Components call dispatch with an action, React runs the reducer, and the returned state becomes the next render snapshot.

💻 Example

useReducer for a Multi-Step Form — A reducer keeps multi-event state transitions explicit and testable.

tsx
import { useReducer } from 'react';

type FormState = {
  step: 1 | 2 | 3;
  saving: boolean;
  error: string | null;
  values: {
    name: string;
    email: string;
  };
};

type FormAction =
  | { type: 'fieldChanged'; field: 'name' | 'email'; value: string }
  | { type: 'nextStep' }
  | { type: 'saveStarted' }
  | { type: 'saveSucceeded' }
  | { type: 'saveFailed'; error: string }
  | { type: 'reset' };

const initialState: FormState = {
  step: 1,
  saving: false,
  error: null,
  values: { name: '', email: '' },
};

function reducer(state: FormState, action: FormAction): FormState {
  switch (action.type) {
    case 'fieldChanged':
      return {
        ...state,
        values: { ...state.values, [action.field]: action.value },
      };
    case 'nextStep':
      return { ...state, step: Math.min(3, state.step + 1) as 1 | 2 | 3 };
    case 'saveStarted':
      return { ...state, saving: true, error: null };
    case 'saveSucceeded':
      return { ...state, saving: false };
    case 'saveFailed':
      return { ...state, saving: false, error: action.error };
    case 'reset':
      return initialState;
    default:
      return state;
  }
}

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

  return (
    <form>
      <input
        value={state.values.name}
        onChange={(e) =>
          dispatch({ type: 'fieldChanged', field: 'name', value: e.target.value })
        }
      />
      <button type="button" onClick={() => dispatch({ type: 'nextStep' })}>
        Continue
      </button>
    </form>
  );
}

🤔 Common Confusions

❓ Should useReducer replace useState everywhere?

No. Use useState for simple local values. Reach for useReducer when the same state can change from many events or when transitions are easier to understand as named actions.

❓ Is useReducer only for global state?

No. It is often most valuable for complex local state inside one component or feature subtree.

🚨 Common Production Issues

Reducers that mutate nested state make debugging and memoization harder. Always return new objects for changed branches.

Action names that describe UI clicks instead of domain intent become noisy over time. Prefer events like saveStarted or itemRemoved.

Keeping async side effects inside the reducer breaks the mental model. Reducers must stay pure.

✅ Best Practices

Use useReducer when several fields or statuses change together.

Keep reducers pure and side-effect free.

Name actions after domain events, not button labels.

Pair useReducer with context only when the state truly needs subtree sharing.

🏛️ Architect's Note

Senior Architect's Perspective

useReducer is a local state-modeling tool. It shines when a screen behaves like a small state machine, but it should not be used to imitate Redux or global architecture without clear need.

💬 Frequently Asked Questions

When do I prefer useReducer over a custom hook?

Often together. A custom hook can expose a reducer-based workflow so consumers get a simple API while the transition logic stays centralized.

🎯 Key Takeaways

1useReducer is best for complex local transitions, not every state value.
2Reducers keep state changes explicit through named actions.
3Pure reducers are easier to test and debug than scattered setter logic.
4Use useState for simple values and useReducer for event-driven workflows.

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these React references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide React learning path

Browse the full React topic collection and learning order on SplashRide.

React Interview Prep

Practice React interview questions and scenario-based answers.

Frontend Engineer Career Path

A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.

Python Developer Career Path

A practical route through Python fundamentals, automation, backend basics, testing, projects, interview preparation, and career growth.

useState

Master useState — the most fundamental React hook. Learn how it works under the hood, the patterns senior engineers use, and the bugs that trip up even experienced developers.

Custom Hooks

Master custom hooks — the most powerful pattern in React for sharing stateful logic between components. Learn how to extract, compose, and test custom hooks the way senior engineers do in enterprise applications.