# The Fundamental Role of a Reducer in React: A Complete Guide

> Discover the fundamental role of a reducer in React. This pure function manages state transitions predictably using the useReducer Hook. Learn how to implement efficient state management.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: deep-dive
- Published: 2026-02-16

---

**A reducer in React is a pure function that accepts the current state and an action object, then returns a new state, enabling predictable state transitions through the `useReducer` Hook.**

The concept of a reducer in React is central to managing complex state logic within functional components. According to the facebook/react source code, the `useReducer` Hook leverages this pattern to provide a deterministic alternative to `useState`, particularly when state updates depend on previous values or involve intricate data transformations.

## What Is a Reducer in React?

At its core, a **reducer** is a pure function with the signature `(state, action) => newState`. It receives the previous state and an action object (or any value), then computes and returns the next state without mutating the original inputs.

When you invoke the `useReducer` Hook, React creates a **dispatch** function that enqueues actions. During the next render cycle, React processes these queued actions by running the reducer with the current state and the action, producing the **next state**. This architecture ensures that all state transitions flow through a single, centralized function, making updates predictable and easy to test.

## How React Implements the Reducer Pattern

### The Dispatcher Architecture

Internally, React implements `useReducer` by delegating to a **dispatcher** that selects the correct implementation based on the component’s current phase. In [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js), the public hook simply forwards to this dispatcher:

```javascript
// ReactHooks.js#L73-L80
export function useReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg, init);
}

```

This delegation allows React to choose between mounting, updating, or rerendering logic while exposing a stable API to developers.

### Mounting and Updating State

The actual state management occurs in [`packages/react-reconciler/src/ReactFiberHooks.js`](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js). During the initial render, the dispatcher calls `mountReducer`, which creates a hook object, stores the initial state, and builds an update **queue** to hold pending actions:

```javascript
// ReactFiberHooks.js#L56-L90 (conceptual)
function mountReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = mountWorkInProgressHook();
  let initialState;
  if (init !== undefined) {
    initialState = init(initialArg);
  } else {
    initialState = initialArg;
  }
  hook.memoizedState = initialState;
  // ... queue setup
  return [hook.memoizedState, dispatch];
}

```

On subsequent renders, `updateReducerImpl` processes the queued actions through the same reducer function to produce the new state:

```javascript
// ReactFiberHooks.js#L102-L118 (conceptual)
function updateReducerImpl<S, A>(
  hook: Hook,
  current: Hook,
  reducer: (S, A) => S,
): [S, Dispatch<A>] {
  // Process pending actions in queue
  let newState = current.memoizedState;
  // Apply reducer to each action
  // ...
  hook.memoizedState = newState;
  return [hook.memoizedState, dispatch];
}

```

This architecture ensures that the reducer serves as the **single source of truth** for state transitions, regardless of when or how many actions are dispatched between renders.

## Practical Example: Counter with useReducer

The following example demonstrates a basic counter implemented with `useReducer`. The reducer handles two action types, ensuring all state logic remains centralized and predictable:

```tsx
import React, {useReducer} from 'react';

// Define the shape of the state and actions
type State = {count: number};
type Action = {type: 'increment'} | {type: 'decrement'};

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

export default function Counter() {
  const [state, dispatch] = useReducer(counterReducer, {count: 0});

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({type: 'decrement'})}>‑</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </div>
  );
}

```

*The `counterReducer` receives the previous `state` and an `action`, returns the new state, and never mutates its inputs.*

## Advanced Pattern: Lazy Initialization

React's `useReducer` supports **lazy initialization** through an optional third argument. This pattern defers expensive initial state calculations until the initial render, preventing unnecessary work on subsequent renders. Internally, `mountReducer` checks for this `init` function and invokes it only once during the initial render.

```tsx
function init(initialValue: number): {count: number} {
  // Expensive calculation could go here
  return {count: initialValue};
}

function reducer(state: {count: number}, action: {type: 'reset'}): {count: number} {
  switch (action.type) {
    case 'reset':
      return init(0);
    default:
      return state;
  }
}

export function LazyCounter() {
  const [state, dispatch] = useReducer(reducer, 0, init);
  return (
    <>
      <p>{state.count}</p>
      <button onClick={() => dispatch({type: 'reset'})}>Reset</button>
    </>
  );
}

```

*The third argument (`init`) is called only once to compute the initial state, matching the internal logic in `mountReducer` where React checks for an `init` function and invokes it lazily.*

## Summary

- A **reducer in React** is a pure function `(state, action) => newState` that centralizes state transition logic.
- The `useReducer` Hook creates a **dispatch** function that queues actions; React processes these actions during the next render by running the reducer.
- Internally, React delegates to a **dispatcher** that selects `mountReducer` or `updateReducerImpl` based on the component lifecycle, as seen in [`ReactHooks.js`](https://github.com/facebook/react/blob/main/ReactHooks.js) and [`ReactFiberHooks.js`](https://github.com/facebook/react/blob/main/ReactFiberHooks.js).
- Reducers enforce **immutability** and **predictability**, making state changes easier to test and debug compared to direct mutation.
- **Lazy initialization** via a third argument to `useReducer` allows expensive initial state calculations to run only once during the initial render.

## Frequently Asked Questions

### What makes a reducer function "pure" in React?

A reducer is considered **pure** when it returns the same output given the same inputs (state and action) without causing side effects. It must not mutate the existing state object directly; instead, it returns a new state object. This determinism allows React to optimize rendering and makes the component's behavior predictable and easy to unit test.

### How does useReducer differ from useState?

While `useState` is ideal for primitive values or simple updates, `useReducer` is better suited for **complex state logic** that involves multiple sub-values or depends on previous state. `useReducer` provides a **dispatch** function with a stable reference, preventing unnecessary re-renders when passing callbacks to child components. Additionally, `useReducer` supports **lazy initialization**, whereas `useState` requires calling a function to achieve similar behavior.

### Can I use useReducer without Redux?

Yes, `useReducer` is a built-in React Hook that operates **locally within a component** without requiring Redux or any external library. It implements the same reducer pattern that Redux popularized—managing state through actions and pure functions—but scoped to a single component or passed down via props or context. This makes it ideal for local state management that follows Redux patterns without the boilerplate of a global store.

### When should I choose useReducer over useState?

Choose `useReducer` when your state logic involves **multiple related values**, requires **complex update logic** (such as toggling items in arrays or merging objects), or when the **next state depends heavily on the previous state**. It is also preferable when you need to **optimize performance** by passing a stable dispatch function to deeply nested child components, avoiding the re-render issues common with inline callbacks used with `useState`.