facebook/react · error

Could not find helpers for a root. This is a bug in React Re

Error message

Could not find helpers for a root. This is a bug in React Refresh.

What it means

During performReactRefresh(), the runtime snapshots failedRoots and helpersByRoot and requires every failed root to have helper functions (scheduleRefresh/scheduleRoot) registered by the commit hook that injectIntoGlobalHook() installed (helpersByRoot.set in ReactFreshRuntime.js:535). This iteration over failedRoots throws when a root is tracked without registered helpers. The message says 'This is a bug in React Refresh': it indicates duplicated runtimes or version skew inside the refresh machinery, not application misuse.

Source

Thrown at packages/react-refresh/src/ReactFreshRuntime.js:271

      // This ensures that if *new* roots are mounted, they'll use the resolve handler.
      helpers.setRefreshHandler(resolveFamily);
    });

    let didError = false;
    let firstError = null;

    // We snapshot maps and sets that are mutated during commits.
    // If we don't do this, there is a risk they will be mutated while
    // we iterate over them. For example, trying to recover a failed root
    // may cause another root to be added to the failed list -- an infinite loop.
    const failedRootsSnapshot = cloneSet(failedRoots);
    const mountedRootsSnapshot = cloneSet(mountedRoots);
    const helpersByRootSnapshot = cloneMap(helpersByRoot);

    failedRootsSnapshot.forEach(root => {
      const helpers = helpersByRootSnapshot.get(root);
      if (helpers === undefined) {
        throw new Error(
          'Could not find helpers for a root. This is a bug in React Refresh.',
        );
      }
      if (!failedRoots.has(root)) {
        // No longer failed.
      }
      if (rootElements === null) {
        return;
      }
      if (!rootElements.has(root)) {
        return;
      }
      const element = rootElements.get(root);
      try {
        helpers.scheduleRoot(root, element);
      } catch (err) {
        if (!didError) {
          didError = true;

View on GitHub (pinned to eafeac097b)

Solutions

  1. Dedupe react, react-dom and react-refresh to a single version each (yarn resolutions, npm overrides, or bundler aliases)
  2. Ensure injectIntoGlobalHook is called exactly once per JS context by one runtime copy
  3. Align the react-refresh major with the react-dom version in use and reinstall
  4. If versions are aligned and it still throws, capture a minimal repro and report it to facebook/react — the message explicitly marks it as a React bug

Example fix

// before: two copies resolve
// package.json (npm)
{
  "overrides": {
    "react-refresh": "0.14.2"
  }
}

// after: verify single copy after reinstall
// $ npm ls react-refresh  -> exactly one version, hoisted at root
Defensive patterns

Strategy: validation

Validate before calling

// Verify only one react-refresh copy is resolvable before enabling HMR
const paths = require.resolve.paths('react-refresh');
const copies = require('child_process')
  .execSync('npm ls react-refresh --all || true')
  .toString();
if ((copies.match(/react-refresh@/g) || []).length > 1) {
  throw new Error('Duplicate react-refresh detected - dedupe before enabling Fast Refresh');
}

Try / catch

try {
  performReactRefresh();
} catch (e) {
  if (e instanceof Error && e.message.includes('bug in React Refresh')) {
    // internal state corruption from duplicate/skewed runtimes: log and disable hot refresh
  } else throw e;
}

Prevention

When it happens

Trigger: Two copies of react-refresh in one JS context — one injected into the global hook, another executing performReactRefresh; roots committed by a renderer whose helpers were registered by a different react/react-dom version (one runtime fills mountedRoots, a mismatched runtime iterates helpersByRoot); calling injectIntoGlobalHook twice from different runtime copies.

Common situations: Monorepos where react-dom and a second renderer (react-native-web, react-three-fiber, ink) each pull their own react-refresh; partial upgrades leaving react, react-dom and react-refresh on mismatched versions; bundler dedupe disabled so two react-refresh instances resolve.

Related errors


AI-assisted analysis of facebook/react@eafeac097b (2026-08-21). Data as JSON: /api/errors/2c962ecab7461364. Report an issue: GitHub.