How to use React Context effectively

By Everett Quebral
Picture of the author
Published on
image alt attribute

How to Use React Context Effectively

Managing state in a scalable React application requires clarity and simplicity. When you need to pass data deeply without prop-drilling—or manage global themes, auth, or settings—React Context is a powerful solution.

But using Context improperly can lead to performance issues or overly complex code.

In this post, you’ll learn:

  • What React Context is and when to use it
  • How to create a Context Provider and access its value
  • Common anti-patterns and how to avoid them
  • Real-world examples (theme toggler, auth, settings)
  • Visual diagram of how Context flows through components

What Is React Context?

React Context allows you to share state across the component tree without passing props manually at every level.

It’s best used for application-level state that multiple components need to access—like the current theme, authentication status, or language preferences.


When to Use Context

Ideal for:

  • Theme switching (light/dark mode)
  • Authentication and user roles
  • Language and locale
  • Global settings or configurations
  • Shared refs or WebSocket connections

⚠️ Not ideal for frequently changing UI state like form fields or animation triggers.


Diagram: How Context Flows

Step-by-Step: Creating and Using Context

1. Create the Context

// theme-context.tsx
import { createContext, useContext, useState, ReactNode } from 'react'

type Theme = 'light' | 'dark'

interface ThemeContextType {
  theme: Theme
  toggleTheme: () => void
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined)

export const ThemeProvider = ({ children }: { children: ReactNode }) => {
  const [theme, setTheme] = useState<Theme>('light')

  const toggleTheme = () => {
    setTheme((prev) => (prev === 'light' ? 'dark' : 'light'))
  }

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

export const useTheme = () => {
  const context = useContext(ThemeContext)
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider')
  }
  return context
}

2. Wrap Your App in the Provider

// App.tsx
import { ThemeProvider } from './theme-context'
import Page from './Page'

function App() {
  return (
    <ThemeProvider>
      <Page />
    </ThemeProvider>
  )
}

export default App

3. Consume Context in Any Component

// Header.tsx
import { useTheme } from './theme-context'

function Header() {
  const { theme, toggleTheme } = useTheme()

  return (
    <header className={`p-4 ${theme === 'dark' ? 'bg-black text-white' : 'bg-white text-black'}`}>
      <h1>Current Theme: {theme}</h1>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </header>
  )
}

Best Practices

Split Contexts by Concern
Don’t create one massive context for all global state. Create separate ones for theme, auth, user settings, etc.

Use Custom Hooks
Wrap useContext in a custom hook (useTheme, useAuth) to enforce proper usage and error handling.

Avoid Unnecessary Re-renders
Context changes trigger a re-render in all consuming components. Minimize value updates or use useMemo:

const value = useMemo(() => ({ theme, toggleTheme }), [theme])

Use Context for Read-Only Data When Appropriate
Read-only context (like config or locale) is inexpensive and easy to use across the app.


Real-World Example: Auth Context

// auth-context.tsx
const AuthContext = createContext<AuthUser | null>(null)

export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const user = useFakeAuth() // simulate or fetch auth
  return <AuthContext.Provider value={user}>{children}</AuthContext.Provider>
}

export const useAuth = () => useContext(AuthContext)

Then in a component:

const user = useAuth()
return user ? <p>Welcome, {user.name}</p> : <LoginForm />

When to Avoid Context

🚫 Rapidly Changing Values
Avoid using context for frequently updated state like animation frames, form inputs, or timers.

🚫 Prop Drilling One Level
If you're just passing props one or two levels down, it’s better to use props for clarity.


Final Thoughts

React Context is a clean, powerful abstraction for global state. When used wisely, it keeps your codebase readable, testable, and scalable.

To use Context effectively:

  • Create providers per domain (auth, theme, settings)
  • Wrap useContext in custom hooks
  • Memoize context values to avoid extra renders
  • Don’t overuse it—combine with state management libraries when needed

Resources

Stay Tuned

Want to become a Next.js pro?
The best articles, links and news related to web development delivered once a week to your inbox.