{"record":{"id":"6b14d3b8f5f027ff","repo":"reduxjs/redux","slug":"reducers-may-not-dispatch-actions","errorCode":null,"errorMessage":"Reducers may not dispatch actions.","messagePattern":"Reducers may not dispatch actions\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/createStore.ts","lineNumber":294,"sourceCode":"      )\n    }\n\n    if (typeof action.type === 'undefined') {\n      throw new Error(\n        'Actions may not have an undefined \"type\" property. You may have misspelled an action type string constant.'\n      )\n    }\n\n    if (typeof action.type !== 'string') {\n      throw new Error(\n        `Action \"type\" property must be a string. Instead, the actual type was: '${kindOf(\n          action.type\n        )}'. Value was: '${String(action.type)}' (stringified)`\n      )\n    }\n\n    if (isDispatching) {\n      throw new Error('Reducers may not dispatch actions.')\n    }\n\n    try {\n      isDispatching = true\n      currentState = currentReducer(currentState, action)\n    } finally {\n      isDispatching = false\n    }\n\n    const listeners = (currentListeners = nextListeners)\n    listeners.forEach(listener => {\n      listener()\n    })\n    return action\n  }\n\n  /**\n   * Replaces the reducer currently used by the store to calculate the state.","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/reduxjs/redux/blob/3084fc33bb23ab4cab4796172eafe342da4e73d9/src/createStore.ts#L276-L312","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Never dispatch from a reducer. Move chained logic to middleware (redux-thunk, redux-saga, redux-observable) where dispatching is allowed.","If an action should trigger another, dispatch the follow-up from the action creator or a thunk, not from the reducer.","For derived state, compute it with selectors (reselect) instead of dispatching a 'sync' action.","In tests, dispatch follow-up actions sequentially outside the reducer call."],"exampleFix":"// before\nfunction reducer(state, action) {\n  if (action.type === 'LOGIN') {\n    store.dispatch({ type: 'FETCH_PROFILE' })  // throws: re-entrant dispatch\n  }\n  return state\n}\n\n// after (in a thunk, outside the reducer)\nconst login = creds => async (dispatch, getState) => {\n  await dispatch({ type: 'LOGIN', payload: creds })\n  dispatch({ type: 'FETCH_PROFILE' })\n}","handlingStrategy":"validation","validationCode":"// Reducers must be pure: never dispatch inside them.\n// Move chained dispatches to a thunk:\nconst chainedAction = () => async (dispatch, getState) => {\n  await dispatch(firstAction())\n  dispatch(secondAction())  // safe: outside reducer\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never call store.dispatch() inside a reducer.","Use middleware (redux-thunk/saga/observable) for action chaining.","Compute derived state with selectors, not by dispatching sync actions.","Lint reducers to flag any dispatch call."],"tags":["redux","createstore","dispatch","reducer","re-entrant","purity","isdispatching"],"backgroundTag":null,"analyzedSha":"3084fc33bb23ab4cab4796172eafe342da4e73d9","analyzedAt":"2026-08-12T13:21:41.727Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}