React - Context API

Context API

Context lets you pass data through the component tree without passing props down manually at every level.

When to use it

  • Many components across different nesting levels need the same data (theme, user, i18n).
  • You’d otherwise pass the same prop through multiple layers (prop drilling).
  • You want a single source of truth for a concern at the app or sub-tree level.

Try it: Theme Context

View source
const { createContext, useContext, useState } = React;
const ThemeContext = createContext('light');

function Toolbar(){
  return (
    <div style={{display:'flex', gap:8}}>
      <ThemedButton>Primary</ThemedButton>
      <ThemedButton>Secondary</ThemedButton>
    </div>
  );
}
function ThemedButton({ children }){
  const theme = useContext(ThemeContext);
  const styles = theme === 'dark' ? { background:'#111827', color:'#e5e7eb' } : { background:'#e5e7eb', color:'#111827' };
  return <button style={{...styles, border:'1px solid #1f2937', padding:'6px 10px', borderRadius:6}}>{children}</button>;
}
function App(){
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={theme}>
      <p>Current theme: <strong>{theme}</strong></p>
      <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>Toggle Theme</button>
      <div style={{marginTop:10}}>
        <Toolbar />
      </div>
    </ThemeContext.Provider>
  );
}
ReactDOM.createRoot(document.getElementById('try-context')).render(<App />);

Syntax primer (for newcomers)

  • createContext(defaultValue): Creates a Context object.
  • <Context.Provider value={...}>: Makes a value available to descendants.
  • useContext(Context): Reads the nearest provider’s value.

Common pitfalls

  • Context updates re-render consumers; avoid storing frequently changing big objects.
  • Don’t nest excessive providers for unrelated concerns; split contexts.

Vocabulary

  • Context: Mechanism to pass data through the tree without props.
  • Provider: Component that supplies a context value.
  • Consumer: Component reading a context value (via useContext).

Prop drilling vs Context

Without Context, props must be threaded through every intermediate component:

// Prop drilling
function App(){
  const user = { name: 'Ada' };
  return ; // pass down
}
function Layout({ user }){ return ; }
function Sidebar({ user }){ return ; }
function UserBadge({ user }){ return {user.name}; }

With Context, intermediate components don’t need to know about the data:

const UserContext = React.createContext(null);
function App(){
  const user = { name: 'Ada' };
  return ;
}
function Layout(){ return ; }
function Sidebar(){ return ; }
function UserBadge(){ const user = React.useContext(UserContext); return {user.name}; }

Exercises

  1. Add a secondary context for language ("en"/"es") and show localized button labels.
  2. Split theme settings into two contexts: one for the theme value and one for a toggleTheme() function.
  3. Lift the provider up a level and verify that deeply nested components can still read the context.

Checks for Understanding

  1. When would you prefer Context over prop drilling?
  2. What causes Context consumers to re-render?
Show answers
  1. When many components at different nesting levels need the same data.
  2. Changes to the provided value (by identity or content) cause consumers to re-render.