{"record":{"id":"0341aaf0fad18848","repo":"remix-run/react-router","slug":"a-history-only-accepts-one-active-listener","errorCode":null,"errorMessage":"A history only accepts one active listener","messagePattern":"A history only accepts one active listener","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/react-router/lib/router/history.ts","lineNumber":749,"sourceCode":"    if (v5Compat && listener) {\n      listener({ action, location: history.location, delta: 0 });\n    }\n  }\n\n  function createURL(to: To): URL {\n    return createBrowserURLImpl(window, to);\n  }\n\n  let history: History = {\n    get action() {\n      return action;\n    },\n    get location() {\n      return getLocation(window, globalHistory);\n    },\n    listen(fn: Listener) {\n      if (listener) {\n        throw new Error(\"A history only accepts one active listener\");\n      }\n      window.addEventListener(PopStateEventType, handlePop);\n      listener = fn;\n\n      return () => {\n        window.removeEventListener(PopStateEventType, handlePop);\n        listener = null;\n      };\n    },\n    createHref(to) {\n      return createHref(window, to);\n    },\n    createURL,\n    encodeLocation(to) {\n      // Encode a Location the same way window.location would\n      let url = createURL(to);\n      return {\n        pathname: url.pathname,","sourceCodeStart":731,"sourceCodeEnd":767,"githubUrl":"https://github.com/remix-run/react-router/blob/1fd704a7dabcbe3ae09d7387b460e6acaba30ec1/packages/react-router/lib/router/history.ts#L731-L767","documentation":"The history implementation keeps a single `listener` slot. Calling `listen()` a second time while a previous listener is still active rejects the attempt. This is a guard against leaking listeners and double-handling popstate events.","triggerScenarios":"Two `history.listen(fn)` calls in a row without invoking the unsubscribe function returned by the first. Commonly happens when constructing a second `<Router>` (or calling `createBrowserRouter` again) on the same history, or when a React effect adds a listener without cleaning up the prior render's listener.","commonSituations":"Hot-module reloading that re-runs an effect which calls `listen` but whose cleanup didn't run; mounting two router instances against one history in tests; SSR/hydration where a listener is attached server-side then again on the client in the same JS realm.","solutions":["Always store and invoke the unsubscribe function returned by `listen()` before adding a new listener.","In React effects, `return () => unlisten()` from the effect so cleanup runs before re-subscription.","Use a single router/history instance per window; don't re-create `createBrowserHistory()` repeatedly.","For tests, create a fresh history (or use `createMemoryHistory`) per test."],"exampleFix":"// before\nconst unlisten = history.listen(fnA);\nhistory.listen(fnB); // throws\n\n// after\nconst unlistenA = history.listen(fnA);\nunlistenA();\nconst unlistenB = history.listen(fnB);","handlingStrategy":"validation","validationCode":"let currentUnlisten: (() => void) | undefined;\nfunction listenOnce(history: History, fn: Listener) {\n  currentUnlisten?.();\n  currentUnlisten = history.listen(fn);\n  return () => { currentUnlisten = undefined; currentUnlisten?.(); };\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always return the unsubscribe function from React effects.","Use one router/history instance per window.","In tests, create a fresh memory history per case."],"tags":["history","listeners","router"],"backgroundTag":null,"analyzedSha":"1fd704a7dabcbe3ae09d7387b460e6acaba30ec1","analyzedAt":"2026-08-12T13:54:57.804Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}