Appearance
Context (provide / inject with extra steps)
Same job as Vue's provide/inject: pass data through the tree without prop-drilling. Slightly more boilerplate. Easy to overuse.
The pattern
tsx
import { createContext, useContext, useState, type ReactNode } from 'react'
type Theme = 'light' | 'dark'
const ThemeContext = createContext<Theme | null>(null)
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>('light')
return (
<ThemeContext.Provider value={theme}>
<button type="button" onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle
</button>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
const theme = useContext(ThemeContext)
if (theme === null) {
throw new Error('useTheme must be used inside ThemeProvider')
}
return theme
}Comments for the code above:
createContext+Provider≈provideuseContext≈inject- Wrapping a custom hook that throws if missing is a nice habit (fail loud)
Consuming it
tsx
function Headline() {
const theme = useTheme()
return <h1 data-theme={theme}>Hello</h1>
}
// somewhere near the root
<ThemeProvider>
<Headline />
</ThemeProvider>Passing value + setter
Often you want read + update. Pack an object (or split contexts - advanced optimization):
tsx
type ThemeCtx = {
theme: Theme
setTheme: (t: Theme) => void
}
const ThemeContext = createContext<ThemeCtx | null>(null)
// Provider value:
<ThemeContext.Provider value={{ theme, setTheme }}>Watch out: a new object every render re-renders all consumers. For theme toggles it's usually fine. For hot high-frequency values, people memoize or use a store (Zustand) instead.
When Context is enough
- Theme, locale, current user session
- Dependency injection for a small app module
- Avoiding 4 layers of prop drilling for one value
When I'd skip Context
- Server cache / async lists → TanStack Query
- Client global UI state with lots of updates → Zustand
- Anything that changes every keystroke and fans out to a huge tree
As you can see - Context is glue, not a state management framework. Vue folks who used provide/inject for everything sometimes try to recreate Pinia with Context. Don't. Use a store.
Next: React Router - your Vue Router instincts mostly transfer.
Best,
-- ł.