{"record":{"id":"2c962ecab7461364","repo":"facebook/react","slug":"could-not-find-helpers-for-a-root-this-is-a-bug-i","errorCode":null,"errorMessage":"Could not find helpers for a root. This is a bug in React Refresh.","messagePattern":"Could not find helpers for a root\\. This is a bug in React Refresh\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/react-refresh/src/ReactFreshRuntime.js","lineNumber":271,"sourceCode":"      // This ensures that if *new* roots are mounted, they'll use the resolve handler.\n      helpers.setRefreshHandler(resolveFamily);\n    });\n\n    let didError = false;\n    let firstError = null;\n\n    // We snapshot maps and sets that are mutated during commits.\n    // If we don't do this, there is a risk they will be mutated while\n    // we iterate over them. For example, trying to recover a failed root\n    // may cause another root to be added to the failed list -- an infinite loop.\n    const failedRootsSnapshot = cloneSet(failedRoots);\n    const mountedRootsSnapshot = cloneSet(mountedRoots);\n    const helpersByRootSnapshot = cloneMap(helpersByRoot);\n\n    failedRootsSnapshot.forEach(root => {\n      const helpers = helpersByRootSnapshot.get(root);\n      if (helpers === undefined) {\n        throw new Error(\n          'Could not find helpers for a root. This is a bug in React Refresh.',\n        );\n      }\n      if (!failedRoots.has(root)) {\n        // No longer failed.\n      }\n      if (rootElements === null) {\n        return;\n      }\n      if (!rootElements.has(root)) {\n        return;\n      }\n      const element = rootElements.get(root);\n      try {\n        helpers.scheduleRoot(root, element);\n      } catch (err) {\n        if (!didError) {\n          didError = true;","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/facebook/react/blob/eafeac097ba51e1eab809c07102126bd5f8e5425/packages/react-refresh/src/ReactFreshRuntime.js#L253-L289","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Dedupe react, react-dom and react-refresh to a single version each (yarn resolutions, npm overrides, or bundler aliases)","Ensure injectIntoGlobalHook is called exactly once per JS context by one runtime copy","Align the react-refresh major with the react-dom version in use and reinstall","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"],"exampleFix":"// before: two copies resolve\n// package.json (npm)\n{\n  \"overrides\": {\n    \"react-refresh\": \"0.14.2\"\n  }\n}\n\n// after: verify single copy after reinstall\n// $ npm ls react-refresh  -> exactly one version, hoisted at root","handlingStrategy":"validation","validationCode":"// Verify only one react-refresh copy is resolvable before enabling HMR\nconst paths = require.resolve.paths('react-refresh');\nconst copies = require('child_process')\n  .execSync('npm ls react-refresh --all || true')\n  .toString();\nif ((copies.match(/react-refresh@/g) || []).length > 1) {\n  throw new Error('Duplicate react-refresh detected - dedupe before enabling Fast Refresh');\n}","typeGuard":null,"tryCatchPattern":"try {\n  performReactRefresh();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('bug in React Refresh')) {\n    // internal state corruption from duplicate/skewed runtimes: log and disable hot refresh\n  } else throw e;\n}","preventionTips":["Pin single versions of react, react-dom and react-refresh via overrides/resolutions","Call injectIntoGlobalHook exactly once per JS context","Run npm ls react-refresh (or yarn why) in CI to catch duplicate copies early"],"tags":["react-refresh","fast-refresh","duplicate-packages","version-mismatch","react-internals"],"backgroundTag":"fast-refresh-internal-state-corruption","analyzedSha":"eafeac097ba51e1eab809c07102126bd5f8e5425","analyzedAt":"2026-08-21T22:01:08.818Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}