{"record":{"id":"d8de90d06478e357","repo":"reduxjs/redux","slug":"you-may-not-unsubscribe-from-a-store-listener-whil","errorCode":null,"errorMessage":"You may not unsubscribe from a store listener while the reducer is executing. See https://redux.js.org/api/store#subscribelistener for more details.","messagePattern":"You may not unsubscribe from a store listener while the reducer is executing\\. See https://redux\\.js\\.org/api/store#subscribelistener for more details\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/createStore.ts","lineNumber":231,"sourceCode":"          'If you would like to be notified after the store has been updated, subscribe from a ' +\n          'component and invoke store.getState() in the callback to access the latest state. ' +\n          'See https://redux.js.org/api/store#subscribelistener for more details.'\n      )\n    }\n\n    let isSubscribed = true\n\n    ensureCanMutateNextListeners()\n    const listenerId = listenerIdCounter++\n    nextListeners.set(listenerId, listener)\n\n    return function unsubscribe() {\n      if (!isSubscribed) {\n        return\n      }\n\n      if (isDispatching) {\n        throw new Error(\n          'You may not unsubscribe from a store listener while the reducer is executing. ' +\n            'See https://redux.js.org/api/store#subscribelistener for more details.'\n        )\n      }\n\n      isSubscribed = false\n\n      ensureCanMutateNextListeners()\n      nextListeners.delete(listenerId)\n      currentListeners = null\n    }\n  }\n\n  /**\n   * Dispatches an action. It is the only way to trigger a state change.\n   *\n   * The `reducer` function, used to create the store, will be called with the\n   * current state tree and the given `action`. Its return value will","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/reduxjs/redux/blob/3084fc33bb23ab4cab4796172eafe342da4e73d9/src/createStore.ts#L213-L249","documentation":"The unsubscribe function returned by subscribe() is also guarded by isDispatching. Removing a listener while the iteration is mid-flight could skip listeners that should have fired. Redux requires unsubscription to happen outside the dispatch window so the snapshot stays consistent for the whole dispatch.","triggerScenarios":"A listener that immediately unsubscribes itself synchronously (calling the returned unsubscribe inside its own callback while still inside the dispatch loop); a reducer that captures and calls an unsubscribe handle; middleware that unsubscribes during next(action).","commonSituations":"Single-fire subscription patterns (`const unsub = store.subscribe(() => { unsub(); ... })`) invoked during the dispatch; cleanup logic that runs during a dispatch path; race between React unmount and an in-flight dispatch.","solutions":["Defer self-unsubscription to after the dispatch: wrap the unsubscribe in queueMicrotask/setTimeout/Promise.resolve().then().","Use the `isSubscribed` boolean guard the unsubscribe already provides (calling twice is a no-op), but schedule the first call outside dispatch.","For single-fire subscriptions, set a flag in the callback and unsubscribe in a microtask."],"exampleFix":"// before\nconst unsub = store.subscribe(() => {\n  doWork()\n  unsub()            // throws: dispatch in progress\n})\n\n// after\nconst unsub = store.subscribe(() => {\n  doWork()\n  queueMicrotask(unsub)\n})","handlingStrategy":"validation","validationCode":"// Defer self-unsubscription out of the dispatch window:\nconst subscribeOnce = (store, listener) => {\n  let unsub\n  let done = false\n  unsub = store.subscribe(() => {\n    listener()\n    if (!done) { done = true; queueMicrotask(() => unsub()) }\n  })\n  return unsub\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never call unsubscribe() synchronously inside its own listener during a dispatch.","Defer unsubscription with queueMicrotask/setTimeout.","For one-shot subscriptions, use a deferred unsubscribe pattern."],"tags":["redux","createstore","subscribe","unsubscribe","isdispatching"],"backgroundTag":null,"analyzedSha":"3084fc33bb23ab4cab4796172eafe342da4e73d9","analyzedAt":"2026-08-12T13:21:41.727Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}