Lesson 6 of 12

Context & State Management

You will learn how to share data such as the current user or theme across many components with Context, without passing props through every level. You will also see when a state is a better fit.

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>
  )
}
Finished reading?Mark it done to track your progress.

Tip: use the ← and → keys to move between lessons.