mastra-ai/mastra · error · Error

useSankey must be used within Sankey

Error message

useSankey must be used within Sankey

What it means

useSankey reads SankeyControlsContext, which the <Sankey> component populates with control columns, toggleColumn, and reorderColumns. When the hook runs with no provider above it, the context is null and this error is thrown. The Sankey design-system component intentionally shares its context and hooks in one module, so importing the hook is easy but using it outside <Sankey> is invalid.

Source

Thrown at packages/playground-ui/src/ds/components/SankeyChart/sankey-context.tsx:124

  const controlColumns = orderedColumns.map(column => ({ ...column, visible: visibleIds.has(column.id) }));

  return (
    <SankeyControlsContext.Provider value={{ columns: controlColumns, toggleColumn, reorderColumns }}>
      <SankeyRenderContext.Provider
        value={{ graph, enabledColumns, hueMap, usesFixedGeometry: getRecordLayoutWeight !== undefined }}
      >
        {children}
      </SankeyRenderContext.Provider>
    </SankeyControlsContext.Provider>
  );
}

// Context providers and their hooks intentionally share this module.
// eslint-disable-next-line react-refresh/only-export-components
export function useSankey() {
  const context = useContext(SankeyControlsContext);
  if (!context) throw new Error('useSankey must be used within Sankey');
  return context;
}

// eslint-disable-next-line react-refresh/only-export-components
export function useSankeyRenderContext() {
  const context = useContext(SankeyRenderContext);
  if (!context) throw new Error('SankeyChart must be used within Sankey');
  return context;
}

function orderColumns(columns: Array<SankeyChartColumn>, order: Array<string>) {
  const positions = new Map(order.map((id, index) => [id, index]));
  return [...columns].sort(
    (left, right) => (positions.get(left.id) ?? columns.length) - (positions.get(right.id) ?? columns.length),
  );
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Render the component calling useSankey as a descendant of <Sankey>.
  2. Move external controls into the Sankey subtree, or lift the state they need out of the context and pass it via props.
  3. Guard rendering so controls only mount when the Sankey provider is present (e.g., render both together from one parent).
  4. If a portal is used, place the portal inside the Sankey React subtree so context propagates.

Example fix

// before
<div className="layout">
  <SankeyControls /> {/* calls useSankey() — throws */}
  <Sankey data={data} />
</div>

// after
<div className="layout">
  <Sankey data={data}>
    <SankeyControls />
  </Sankey>
</div>
Defensive patterns

Strategy: validation

Validate before calling

const context = useContext(SankeyControlsContext);
if (!context) {
  // render controls disabled or skip rendering instead of calling useSankey-dependent paths
}

Type guard

const hasSankeyControls = (v: ReturnType<typeof useSankey> | null): v is NonNullable<ReturnType<typeof useSankey>> => v != null;

Try / catch

try {
  const controls = useSankey();
  controls.toggleColumn(id);
} catch {
  // outside <Sankey>: render static fallback
}

Prevention

When it happens

Trigger: Calling useSankey() in a custom legend/controls component rendered outside the <Sankey> tree; rendering InvalidControls-like controls in a parent layout that sits above <Sankey>; conditionally mounting <Sankey> while its control sidebar still renders.

Common situations: Repositioning sankey column controls into a separate panel outside the chart; building a storybook story that renders only the controls; refactoring the chart so the provider unmounts before dependent controls.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/4296ed529ddfa911. Report an issue: GitHub.