React Context

Learn how to share data across components and manage global state more effectively.

The problem: prop drilling

Without context, data must thread through every component in between - even ones that don't use it.

Live demo

<App>// owns the colour
color="#EBDEFB"
<Section>// doesn't use it, just passes it down
color="#EBDEFB"
<Button>// finally uses it
My favourite color!

Section must accept and forward color even though it never uses it. Add more levels and this becomes painful fast.

Code

function App() {
  const [color, setColor] =
    useState('#EBDEFB')

  return <Section color={color} />
}

function Section({ color }) {
  return <Button color={color} />
}

function Button({ color }) {
  return (
    <div style={{ background: color }}>
      My favourite color!
    </div>
  )
}

The solution: React Context

Context lets you beam data directly to any descendant - no intermediate props required. There are two steps: providing and consuming.

1

Create the context

Call createContext() once — at the top of a file or in its own module. The argument is the fallback value used when no Provider exists above in the tree.

import { createContext } from 'react'

// The argument is the default value,
// used when there is no Provider above.
export const FavouriteColorContext = createContext<string>('#EBDEFB')
2

Provide - broadcast the value

Wrap the subtree that needs the data in a Provider. Whatever you pass as value is broadcast to all descendants. Change the colour below to see it update live.

Broadcasting:

<FavouriteColorContext.Provider value="#EBDEFB">

<DeepChild>

My favourite color!

no props passed ✓

</FavouriteColorContext.Provider>

function App() {
  const [color, setColor] =
    useState('#EBDEFB')

  return (
    <FavouriteColorContext.Provider
      value={color}
    >
      <Home /> {/* any depth */}
    </FavouriteColorContext.Provider>
  )
}
3

Consume - read with useContext

Any descendant can read the context value without receiving any props. useContext subscribes the component it re-renders automatically when the value changes.

import { useContext } from 'react'
import { FavouriteColorContext } from './App'

function DeepChild() {
  const color = useContext(FavouriteColorContext)

  return (
    <div style={{ background: color }}>
      My favourite color!
    </div>
  )
}
4

Update - pass the setter too

Pass both the value and its setter as the context value. Any descendant can then read and update the shared state. The deep child below does both - try the swatches.

<ColorContext.Provider value={{ color, setColor }}>

<Layout> — no color prop needed

<DeepChild> — reads AND writes

My favourite color!
Change it:

</ColorContext.Provider>

type ColorCtx = {
  color: string
  setColor: (c: string) => void
}
const ColorContext = createContext<ColorCtx>(...)

// Provide both together
<ColorContext.Provider
  value={{ color, setColor }}
>
  <App />
</ColorContext.Provider>

// Deep child reads AND writes
function DeepChild() {
  const { color, setColor } =
    useContext(ColorContext)
}