Documentation

Context Usage

When and how to use React Context — UI state only, never server or async state.

Show:

Framework implementations

Abstract reference: 08-frontend-architecture/02-context-usage.md


The Rule: Context Is for UI State Only

React Context is appropriate for state that is:

  • Synchronous — it is known at render time without fetching
  • UI-scoped — relevant to a subtree of the component tree (theme, sidebar open/closed, selected tab)
  • Not cacheable — it changes frequently or doesn't benefit from React Query caching

React Context is not appropriate for:

  • Server state (use React Query)
  • Data that needs to be fetched async (use React Query)
  • State that is shared across many unrelated parts of the app (consider Zustand or a flatter state approach)

When Context Is Appropriate

Use caseContext?Alternative
Current theme (light/dark)✅ Yes
Sidebar open/closed✅ YesuseState in layout
Modal open/closedSometimesuseState near the trigger
Currently selected account ID (UI selection)✅ Yes
User session / current user data❌ NouseSession() (React Query)
List of accounts❌ NouseAccounts() (React Query)
Auth token❌ NolocalStorage + Axios interceptor

How to Create Context

// contexts/AccountSelectionContext.tsx
'use client'
 
import { createContext, useContext, useState } from 'react'
 
interface AccountSelectionContextValue {
  selectedAccountId: string | null
  selectAccount: (id: string | null) => void
}
 
const AccountSelectionContext = createContext<AccountSelectionContextValue | null>(null)
 
export function AccountSelectionProvider({ children }: { children: React.ReactNode }) {
  const [selectedAccountId, setSelectedAccountId] = useState<string | null>(null)
 
  return (
    <AccountSelectionContext.Provider
      value={{ selectedAccountId, selectAccount: setSelectedAccountId }}
    >
      {children}
    </AccountSelectionContext.Provider>
  )
}
 
// Always export a typed hook, never the raw context object
export function useAccountSelection(): AccountSelectionContextValue {
  const ctx = useContext(AccountSelectionContext)
  if (!ctx) throw new Error('useAccountSelection must be used within AccountSelectionProvider')
  return ctx
}

Usage:

// In a layout that needs the selection:
<AccountSelectionProvider>
  <AccountSidebar />
  <AccountDetail />
</AccountSelectionProvider>
 
// In any child component:
const { selectedAccountId, selectAccount } = useAccountSelection()

Scope Context Tightly

Do not put context providers in the root layout unless the state genuinely needs to be available everywhere. Scope them to the layout or subtree that needs them:

// ✅ Correct — scoped to the admin section only
// app/admin/layout.tsx
export default function AdminLayout({ children }) {
  return (
    <AccountSelectionProvider>
      <AdminShell>{children}</AdminShell>
    </AccountSelectionProvider>
  )
}
 
// ❌ Incorrect — unnecessarily global
// app/providers.tsx
export function Providers({ children }) {
  return (
    <QueryClientProvider ...>
      <AccountSelectionProvider>   {/* Don't add here if only admin needs it */}
        {children}
      </AccountSelectionProvider>
    </QueryClientProvider>
  )
}

Context vs useState

If the state only needs to be shared between a parent and one or two direct children, use useState with prop drilling — it's simpler than context for shallow trees:

// ✅ Fine for 1-2 levels — no context needed
function AdminPage() {
  const [selectedId, setSelectedId] = useState<string | null>(null)
  return (
    <div>
      <AccountSidebar onSelect={setSelectedId} />
      <AccountDetail accountId={selectedId} />
    </div>
  )
}

Use context when the same state needs to be accessed by components at 3+ levels of nesting or across sibling subtrees that don't share a close common ancestor.


Rules

  • Context providers always export a typed custom hook (useXxx) — consumers never call useContext(XxxContext) directly.
  • The custom hook throws if used outside the provider — this makes missing-provider bugs obvious at development time.
  • Never store async or server-fetched data in context — use React Query.
  • Never store data in context that could be derived from React Query (useAccounts, useSession) — this creates duplicate state.
  • Global providers (app/providers.tsx) contain only framework-level providers (QueryClientProvider, ThemeProvider). Feature-specific providers live in the layout that needs them.