{"record":{"id":"1cbf75a85fbe89b1","repo":"facebook/docusaurus","slug":"hook-is-called-outside-the-scrollcontrollerprovid","errorCode":null,"errorMessage":"Hook is called outside the <ScrollControllerProvider>. ","messagePattern":"Hook is called outside the <ScrollControllerProvider>\\. ","errorType":"exception","errorClass":"ReactContextError","httpStatus":null,"severity":"error","filePath":"packages/docusaurus-theme-common/src/utils/scrollUtils.tsx","lineNumber":74,"sourceCode":"  const value = useScrollControllerContextValue();\n  return (\n    <ScrollMonitorContext.Provider value={value}>\n      {children}\n    </ScrollMonitorContext.Provider>\n  );\n}\n\n/**\n * We need a way to update the scroll position while ignoring scroll events\n * so as not to toggle Navbar/BackToTop visibility.\n *\n * This API permits to temporarily disable/ignore scroll events. Motivated by\n * https://github.com/facebook/docusaurus/pull/5618\n */\nexport function useScrollController(): ScrollController {\n  const context = useContext(ScrollMonitorContext);\n  if (context == null) {\n    throw new ReactContextError('ScrollControllerProvider');\n  }\n  return context;\n}\n\ntype ScrollPosition = {scrollX: number; scrollY: number};\n\nconst getScrollPosition = (): ScrollPosition | null =>\n  ExecutionEnvironment.canUseDOM\n    ? {\n        scrollX: window.pageXOffset,\n        scrollY: window.pageYOffset,\n      }\n    : null;\n\n/**\n * This hook fires an effect when the scroll position changes. The effect will\n * be provided with the before/after scroll positions. Note that the effect may\n * not be always run: if scrolling is disabled through `useScrollController`, it","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/facebook/docusaurus/blob/3f483e80e326cc646b54b83d564b3f0c4881b9a6/packages/docusaurus-theme-common/src/utils/scrollUtils.tsx#L56-L92","documentation":"Thrown by `useScrollController()` when its context is `null`, i.e. the hook was called outside `<ScrollControllerProvider>`. The provider tracks scroll position and exposes pause/resume controls used by the navbar and back-to-top button to ignore programmatic scrolls.","triggerScenarios":"A component consuming `useScrollController` (directly or via `useScrollPosition`/`useScrollListener`) is rendered above/outside the provider in the React tree. The provider is normally mounted near the layout root.","commonSituations":"Swizzling `Layout`/`Navbar` and dropping or relocating `<ScrollControllerProvider>`; rendering the navbar or back-to-top button in a separate root/portal that escapes the provider; isolated tests of scroll-consuming components without the provider.","solutions":["Keep `<ScrollControllerProvider>` as a high ancestor (near the layout root) wrapping navbar and back-to-top button.","In tests, wrap consuming components with the provider.","Restore the provider in any swizzled layout; verify against upstream after upgrades."],"exampleFix":"// before — navbar rendered outside provider\n<Navbar />\n<ScrollControllerProvider>{children}</ScrollControllerProvider>\n// after\n<ScrollControllerProvider>\n  <Navbar />\n  {children}\n</ScrollControllerProvider>","handlingStrategy":"validation","validationCode":"import {useContext} from 'react';\nimport {ScrollMonitorContext} from '@docusaurus/theme-common/internal';\nfunction useScrollControllerSafe() {\n  return useContext(ScrollMonitorContext); // null if outside provider\n}","typeGuard":null,"tryCatchPattern":"try {\n  const scroll = useScrollController();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('ScrollControllerProvider')) return null;\n  throw e;\n}","preventionTips":["Mount `<ScrollControllerProvider>` near the layout root, above navbar and back-to-top button.","Wrap test renders of scroll consumers with the provider.","Restore the provider in swizzled layouts."],"tags":["react","context","scroll","navbar","theme-common"],"backgroundTag":null,"analyzedSha":"3f483e80e326cc646b54b83d564b3f0c4881b9a6","analyzedAt":"2026-08-12T13:25:04.382Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}