Why React Reducers Must Be Pure

React reducers must be pure because React can call them more than once. Learn what purity means and how mutation and side effects break updates.

5 min read

React reducers must be pure because React may run them more than once and may replay them in any order. A pure reducer returns the same state for the same inputs and never touches anything outside itself.

That guarantee is what lets React call the reducer again without changing the result. Any side effect in the reducer turns that safety into a bug you may only see in development.

What pure means for a reducer

A pure function has two rules: the same arguments always produce the same return value, and it does not change anything outside itself. For a reducer, that means no API calls, no timers, no random values, and no mutating the state object it receives. Reading a ref, writing a log, or generating an ID are also side effects, even when they feel harmless.

index.jsindex.js
function reducer(state, action) {
  switch (action.type) {
    case "added":
      state.items.push(action.item);
      return state;
    default:
      return state;
  }
}

This reducer mutates state by pushing into its items array. The next state points at the same, now modified, object, so React sees the old reference and skips the update. In Strict Mode the mutation runs twice, so the item gets pushed twice and the bug becomes obvious.

React may call reducers twice

In Strict Mode, React calls reducers and initializers twice during development. It keeps one result and ignores the other, which is harmless for a pure function but exposes impurity immediately.

index.jsindex.js
function reducer(state, action) {
  switch (action.type) {
    case "added":
      return { ...state, items: [...state.items, action.item] };
    default:
      return state;
  }
}

This version returns a new object with a new items array. Calling it twice produces the same result both times, so the extra call changes nothing on screen. The double call happens only in development, and production calls the reducer once.

Purity is what makes reducers testable

A pure reducer is a closed box: the same state and action always produce the same result. That determinism lets you call the reducer in a test without rendering a component or mocking the browser.

Side effects belong in handlers

A reducer should only decide the next state. Showing an alert, writing to storage, or starting a request belongs in the event handler that dispatches the action.

index.jsindex.js
function handleAdd(item) {
  dispatch({ type: "added", item });
  saveToStorage(item);
}

The handler dispatches a pure action and then performs the side effect. Handlers may be impure and may run once, which is exactly why the impure work belongs there instead of in the reducer. The reducer stays deterministic, which makes it safe to test.

Read how to write actions and reducers for the authoring rules, or testing reducers without rendering components to turn that purity into fast tests.

Rune AI

Rune AI

Key Insights

  • A pure reducer always returns the same output for the same inputs.
  • React may call reducers twice in development.
  • Mutating state in a reducer produces visible bugs.
  • Side effects belong in event handlers.
  • Purity is what makes reducers testable.
RunePowered by Rune AI

Frequently Asked Questions

What does pure mean for a reducer?

A pure reducer returns the same result for the same state and action, and it performs no side effects such as network calls, timers, or random values.

Why does React call reducers twice?

In Strict Mode during development, React calls reducers and initializers twice to surface accidental impurity. Production runs them once.

Can a reducer read the current time or generate random values?

No. Those make the result depend on something outside the reducer, which breaks purity. Move such work into the event handler.

Conclusion

Reducers run during rendering, so they must be pure. React relies on that purity to call them multiple times safely, and any mutation or side effect turns an invisible problem into a visible bug.