remix-run/react-router · error · Error

A history only accepts one active listener

Error message

A history only accepts one active listener

What it means

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.

Source

Thrown at packages/react-router/lib/router/history.ts:749

    if (v5Compat && listener) {
      listener({ action, location: history.location, delta: 0 });
    }
  }

  function createURL(to: To): URL {
    return createBrowserURLImpl(window, to);
  }

  let history: History = {
    get action() {
      return action;
    },
    get location() {
      return getLocation(window, globalHistory);
    },
    listen(fn: Listener) {
      if (listener) {
        throw new Error("A history only accepts one active listener");
      }
      window.addEventListener(PopStateEventType, handlePop);
      listener = fn;

      return () => {
        window.removeEventListener(PopStateEventType, handlePop);
        listener = null;
      };
    },
    createHref(to) {
      return createHref(window, to);
    },
    createURL,
    encodeLocation(to) {
      // Encode a Location the same way window.location would
      let url = createURL(to);
      return {
        pathname: url.pathname,

View on GitHub (pinned to 1fd704a7da)

Solutions

  1. Always store and invoke the unsubscribe function returned by `listen()` before adding a new listener.
  2. In React effects, `return () => unlisten()` from the effect so cleanup runs before re-subscription.
  3. Use a single router/history instance per window; don't re-create `createBrowserHistory()` repeatedly.
  4. For tests, create a fresh history (or use `createMemoryHistory`) per test.

Example fix

// before
const unlisten = history.listen(fnA);
history.listen(fnB); // throws

// after
const unlistenA = history.listen(fnA);
unlistenA();
const unlistenB = history.listen(fnB);
Defensive patterns

Strategy: validation

Validate before calling

let currentUnlisten: (() => void) | undefined;
function listenOnce(history: History, fn: Listener) {
  currentUnlisten?.();
  currentUnlisten = history.listen(fn);
  return () => { currentUnlisten = undefined; currentUnlisten?.(); };
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12). Data as JSON: /api/errors/0341aaf0fad18848. Report an issue: GitHub.