Skip to content

Context API

The Context API is React's built-in mechanism for passing data through the component tree without props at every level. This lesson builds production-ready provider patterns for theme, authentication, and configuration.

Building a Context Module

A well-structured context module exports: the context object (often private), a Provider component wrapping state logic, and a custom hook for consumption with error checking.

Avoid exporting raw context — force consumers through your hook for consistent validation and future refactors.

// auth-context.jsx
const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const value = useMemo(() => ({ user, setUser }), [user]);
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth requires AuthProvider');
  return ctx;
}

Memoized value object prevents re-renders when parent re-renders without user change.

Provider Tree Placement

createRoot(el).render(
  <AuthProvider>
    <ThemeProvider>
      <App />
    </ThemeProvider>
  </AuthProvider>
);
  • Place providers near the root, wrapping routes.
  • Order matters when contexts depend on each other.
  • Nest multiple providers or combine into one AppProviders component.
  • Test components with mock providers in tests.

Context Patterns

Common context architecture choices.

Pattern Use For
Single context Theme, locale
Split contexts Auth vs UI preferences
Provider + hook All production contexts
Default null + throw Catch missing provider early
Memoized value Reduce consumer re-renders
AppProviders wrapper Clean main.jsx

Splitting Contexts

One AuthContext for user session and a separate ThemeContext prevents theme toggles from re-rendering auth-unrelated subtrees when only theme changes — if values are split correctly.

Testing with Providers

Wrap components under test with necessary providers or create test utilities that supply mock context values.

render(<Dashboard />, {
  wrapper: ({ children }) => (
    <AuthProvider initialUser={mockUser}>{children}</AuthProvider>
  ),
});

Common Mistakes

  • New object in Provider value every render without useMemo.
  • One mega-context for entire app state.
  • Exporting context directly instead of custom hook.
  • Forgetting provider above route that needs context.

Key Takeaways

  • Structure context as Provider + custom hook module.
  • Memoize provider values; split by update frequency.
  • Throw helpful errors when hook used outside provider.
  • Wrap app routes with required providers at root.

Pro Tip

Create src/providers/AppProviders.jsx composing all providers — keeps main.jsx readable.