spree/spree · error · Error
useSidebar must be used within a SidebarProvider.
Error message
useSidebar must be used within a SidebarProvider.
What it means
useSidebar() reads SidebarContext, created only by <SidebarProvider> in @spree/dashboard-ui. The provider owns desktop open state, mobile open state, and isMobile detection; every sidebar compound component (trigger, menu, inset) consumes it. Calling the hook outside the provider throws.
Source
Thrown at packages/dashboard-ui/src/ui/sidebar.tsx:45
/**
* Sets the state without remembering it. For collapses the app imposes on
* the user's behalf (e.g. the settings area folding the nav to icons to make
* room for its own): the merchant's own choice, made through the trigger,
* must survive it.
*/
setOpenTransient: (open: boolean) => void
openMobile: boolean
setOpenMobile: (open: boolean) => void
isMobile: boolean
toggleSidebar: () => void
}
const SidebarContext = React.createContext<SidebarContextProps | null>(null)
function useSidebar() {
const context = React.useContext(SidebarContext)
if (!context) {
throw new Error('useSidebar must be used within a SidebarProvider.')
}
return context
}
function SidebarProvider({
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
className,
style,
children,
...props
}: React.ComponentProps<'div'> & {
defaultOpen?: boolean
open?: boolean
onOpenChange?: (open: boolean) => void
}) {View on GitHub (pinned to 06bf66a868)
Solutions
- Wrap the app layout (the part containing the sidebar and its trigger) in <SidebarProvider>
- In tests, render(<SidebarTrigger />, { wrapper: SidebarProvider })
- Keep the trigger inside the provider tree — if the header lives outside, lift the provider up to their common parent
- For pages without a sidebar that still need a collapse button, drive your own state instead of useSidebar()
Example fix
// before
<header><SidebarTrigger /></header> {/* outside the provider */}
<SidebarProvider>
<Sidebar>...</Sidebar>
</SidebarProvider>
// after — provider wraps both sidebar and trigger
<SidebarProvider>
<AppShell>
<Sidebar>...</Sidebar>
<header><SidebarTrigger /></header>
</AppShell>
</SidebarProvider> Defensive patterns
Strategy: validation
Validate before calling
// SidebarProvider must wrap both <Sidebar> and every <SidebarTrigger> — put it
// around the whole app layout:
<SidebarProvider>
<AppShell>{/* Sidebar + header with SidebarTrigger */}</AppShell>
</SidebarProvider> Prevention
- Copy the provider when copying shadcn sidebar markup — the components are unusable without it
- Put the provider at the layout level so any route using the sidebar or its trigger is covered
- Wrap sidebar component tests with SidebarProvider
When it happens
Trigger: Rendering <SidebarTrigger> (or any Sidebar* compound component / custom component calling useSidebar()) outside <SidebarProvider> — e.g. a collapse button placed in a header rendered before the provider, a sidebar widget reused on a non-sidebar page, or a test render without the wrapper.
Common situations: Shadcn-style sidebar adoption in a custom host app: developers copy <Sidebar> markup but skip the provider; unit tests for menu components; refactoring the app shell so the sidebar renders on a route that no longer sits under the provider.
Related errors
- useChart must be used within ChartContainer
- useConfirm must be used within ConfirmProvider
- useTheme must be used within <ThemeProvider>
- CustomFields components must be used within a CustomFieldsPr
- useMap must be used within a Map component
AI-assisted analysis of spree/spree@06bf66a868 (2026-08-21).
Data as JSON: /api/errors/c9895575a687f5b8.
Report an issue: GitHub.