facebook/docusaurus · error · ReactContextError

Hook is called outside the <ScrollControllerProvider>.

Error message

Hook is called outside the <ScrollControllerProvider>. 

What it means

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.

Source

Thrown at packages/docusaurus-theme-common/src/utils/scrollUtils.tsx:74

  const value = useScrollControllerContextValue();
  return (
    <ScrollMonitorContext.Provider value={value}>
      {children}
    </ScrollMonitorContext.Provider>
  );
}

/**
 * We need a way to update the scroll position while ignoring scroll events
 * so as not to toggle Navbar/BackToTop visibility.
 *
 * This API permits to temporarily disable/ignore scroll events. Motivated by
 * https://github.com/facebook/docusaurus/pull/5618
 */
export function useScrollController(): ScrollController {
  const context = useContext(ScrollMonitorContext);
  if (context == null) {
    throw new ReactContextError('ScrollControllerProvider');
  }
  return context;
}

type ScrollPosition = {scrollX: number; scrollY: number};

const getScrollPosition = (): ScrollPosition | null =>
  ExecutionEnvironment.canUseDOM
    ? {
        scrollX: window.pageXOffset,
        scrollY: window.pageYOffset,
      }
    : null;

/**
 * This hook fires an effect when the scroll position changes. The effect will
 * be provided with the before/after scroll positions. Note that the effect may
 * not be always run: if scrolling is disabled through `useScrollController`, it

View on GitHub (pinned to 3f483e80e3)

Solutions

  1. Keep `<ScrollControllerProvider>` as a high ancestor (near the layout root) wrapping navbar and back-to-top button.
  2. In tests, wrap consuming components with the provider.
  3. Restore the provider in any swizzled layout; verify against upstream after upgrades.

Example fix

// before — navbar rendered outside provider
<Navbar />
<ScrollControllerProvider>{children}</ScrollControllerProvider>
// after
<ScrollControllerProvider>
  <Navbar />
  {children}
</ScrollControllerProvider>
Defensive patterns

Strategy: validation

Validate before calling

import {useContext} from 'react';
import {ScrollMonitorContext} from '@docusaurus/theme-common/internal';
function useScrollControllerSafe() {
  return useContext(ScrollMonitorContext); // null if outside provider
}

Try / catch

try {
  const scroll = useScrollController();
} catch (e) {
  if (e instanceof Error && e.message.includes('ScrollControllerProvider')) return null;
  throw e;
}

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of facebook/docusaurus@3f483e80e3 (2026-08-12). Data as JSON: /api/errors/1cbf75a85fbe89b1. Report an issue: GitHub.