ReReact · Lesson 6 of 8

Context & State Management

Context lets you share data across a component tree without passing props through every level. It's great for global state like the current user, theme, or locale — but it's not a replacement for a proper state manager in complex apps.

JSX
import { createContext, useContext, useState } from 'react'

// 1. Create the context
const ThemeContext = createContext({ theme: 'light', toggle: () => {} })

// 2. Provide the context
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light')

  const toggle = () => setTheme(t => t === 'light' ? 'dark' : 'light')

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

// 3. Custom hook to consume it
function useTheme() {
  return useContext(ThemeContext)
}

// 4. Use anywhere in the tree
function Header() {
  const { theme, toggle } = useTheme()
  return (
    <header data-theme={theme}>
      <button onClick={toggle}>Switch to {theme === 'light' ? 'dark' : 'light'}</button>
    </header>
  )
}

function App() {
  return (
    <ThemeProvider>
      <Header />
      <main>...</main>
    </ThemeProvider>
  )
}
JSX
import { createContext, useContext, useReducer } from 'react'

// useReducer — better than useState for complex state logic
const initialState = {
  user: null,
  cart: [],
  notifications: [],
}

function appReducer(state, action) {
  switch (action.type) {
    case "SET_USER":
      return { ...state, user: action.payload }
    case "ADD_TO_CART":
      return { ...state, cart: [...state.cart, action.payload] }
    case "REMOVE_FROM_CART":
      return { ...state, cart: state.cart.filter(i => i.id !== action.payload) }
    case "ADD_NOTIFICATION":
      return { ...state, notifications: [...state.notifications, action.payload] }
    default:
      return state
  }
}

const AppContext = createContext(null)

function AppProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, initialState)
  return (
    <AppContext.Provider value={{ state, dispatch }}>
      {children}
    </AppContext.Provider>
  )
}

// Usage
function CartBadge() {
  const { state, dispatch } = useContext(AppContext)
  return (
    <span>
      Cart: {state.cart.length}
      <button onClick={() => dispatch({ type: "ADD_TO_CART", payload: { id: 1, name: "Item" } })}>
        Add
      </button>
    </span>
  )
}
◆ Note
Context re-renders all consumers when the value changes. For frequently-changing data (like a real-time counter), context can cause performance issues. Use Zustand, Jotai, or Redux Toolkit for complex global state. Context is best for infrequently-changing global config.