reduxjs/redux · error · Error

Reducers may not dispatch actions.

Error message

Reducers may not dispatch actions.

What it means

Dispatching from inside a reducer (a re-entrant dispatch) is forbidden because reducers must be pure functions of (state, action) and must not cause side effects. The isDispatching flag is set true while currentReducer runs; if dispatch() is called again during that window, Redux throws to prevent infinite loops and corrupted state.

Source

Thrown at src/createStore.ts:294

      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
      )
    }

    if (typeof action.type !== 'string') {
      throw new Error(
        `Action "type" property must be a string. Instead, the actual type was: '${kindOf(
          action.type
        )}'. Value was: '${String(action.type)}' (stringified)`
      )
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = (currentListeners = nextListeners)
    listeners.forEach(listener => {
      listener()
    })
    return action
  }

  /**
   * Replaces the reducer currently used by the store to calculate the state.

View on GitHub (pinned to 3084fc33bb)

Solutions

  1. Never dispatch from a reducer. Move chained logic to middleware (redux-thunk, redux-saga, redux-observable) where dispatching is allowed.
  2. If an action should trigger another, dispatch the follow-up from the action creator or a thunk, not from the reducer.
  3. For derived state, compute it with selectors (reselect) instead of dispatching a 'sync' action.
  4. In tests, dispatch follow-up actions sequentially outside the reducer call.

Example fix

// before
function reducer(state, action) {
  if (action.type === 'LOGIN') {
    store.dispatch({ type: 'FETCH_PROFILE' })  // throws: re-entrant dispatch
  }
  return state
}

// after (in a thunk, outside the reducer)
const login = creds => async (dispatch, getState) => {
  await dispatch({ type: 'LOGIN', payload: creds })
  dispatch({ type: 'FETCH_PROFILE' })
}
Defensive patterns

Strategy: validation

Validate before calling

// Reducers must be pure: never dispatch inside them.
// Move chained dispatches to a thunk:
const chainedAction = () => async (dispatch, getState) => {
  await dispatch(firstAction())
  dispatch(secondAction())  // safe: outside reducer
}

Prevention

When it happens

Trigger: A reducer that calls store.dispatch(...) to chain another action; middleware that dispatches inside the reducer phase; a thunk incorrectly invoked synchronously inside a reducer; selector/derived-state code that triggers a dispatch during reduction.

Common situations: Logic that 'auto-responds' to one action by dispatching another from within a reducer; porting event-driven code into reducers; subscriber that dispatches from a path that is itself a reducer callback; testing harness that dispatches inside a mock reducer.

Related errors


AI-assisted analysis of reduxjs/redux@3084fc33bb (2026-08-12). Data as JSON: /api/errors/6b14d3b8f5f027ff. Report an issue: GitHub.