React - Context API (Advanced)
Goal
Use advanced Context patterns to minimize re-renders and scale provider design: split value/dispatch contexts, memoize values, and structure multiple small contexts.
When to use these patterns
- You notice broad re-renders when a provider value changes.
- You want stable dispatch functions and to avoid passing new objects every render.
- You can split one big context into smaller, focused contexts for clarity and performance.
Try it: Split value and dispatch contexts
View source
const { createContext, useContext, useReducer, useMemo } = React;
const ThemeValueContext = createContext('light');
const ThemeDispatchContext = createContext(() => {});
function reducer(state, action){
  switch(action.type){
    case 'toggle': return state === 'light' ? 'dark' : 'light';
    default: return state;
  }
}
function ThemeProvider({ children }){
  const [theme, dispatch] = useReducer(reducer, 'light');
  // value changes when theme changes; dispatch is stable
  const value = theme; // primitive is fine; could also memoize object
  const dispatchStable = useMemo(() => dispatch, [dispatch]);
  return (
    <ThemeDispatchContext.Provider value={dispatchStable}>
      <ThemeValueContext.Provider value={value}>
        {children}
      </ThemeValueContext.Provider>
    </ThemeDispatchContext.Provider>
  );
}
function useTheme(){ return useContext(ThemeValueContext); }
function useThemeDispatch(){ return useContext(ThemeDispatchContext); }
function ToggleButton(){
  const dispatch = useThemeDispatch();
  return <button onClick={() => dispatch({ type:'toggle' })}>Toggle</button>;
}
function ThemedPanel(){
  const theme = useTheme();
  const style = theme === 'dark' ? { background:'#111827', color:'#e5e7eb', padding:10, borderRadius:8 } : { background:'#e5e7eb', color:'#111827', padding:10, borderRadius:8 };
  return <div style={style}>Theme: <strong>{theme}</strong></div>;
}
function App(){
  return (
    <ThemeProvider>
      <div style={{display:'flex', gap:8, alignItems:'center'}}>
        <ToggleButton />
        <ThemedPanel />
      </div>
    </ThemeProvider>
  );
}
ReactDOM.createRoot(document.getElementById('try-context-adv-split')).render(<App />);
Try it: Memoize provider value and use minimal contexts
View source
const { createContext, useContext, useMemo, useState } = React;
const UserNameContext = createContext('');
const UserRoleContext = createContext('guest');
function UserProvider({ children }){
  const [name, setName] = useState('Ada');
  const [role, setRole] = useState('admin');
  // Separate contexts so changing role doesn't re-render consumers that only read name
  const nameValue = useMemo(() => name, [name]);
  const roleValue = useMemo(() => role, [role]);
  return (
    <UserNameContext.Provider value={nameValue}>
      <UserRoleContext.Provider value={roleValue}>
        {children}
      </UserRoleContext.Provider>
    </UserNameContext.Provider>
  );
}
function useUserName(){ return useContext(UserNameContext); }
function useUserRole(){ return useContext(UserRoleContext); }
function NameBadge(){ const name = useUserName(); return <span>Name: <strong>{name}</strong></span>; }
function RoleBadge(){ const role = useUserRole(); return <span>Role: <strong>{role}</strong></span>; }
function App(){
  const [name, setName] = React.useState('Ada');
  const [role, setRole] = React.useState('admin');
  return (
    <UserNameContext.Provider value={name}>
      <UserRoleContext.Provider value={role}>
        <div style={{display:'flex', gap:10, alignItems:'center'}}>
          <NameBadge />
          <RoleBadge />
        </div>
        <div style={{marginTop:10, display:'flex', gap:8}}>
          <input value={name} onChange={e => setName(e.target.value)} placeholder="Name" />
          <select value={role} onChange={e => setRole(e.target.value)}>
            <option value="admin">admin</option>
            <option value="user">user</option>
          </select>
        </div>
      </UserRoleContext.Provider>
    </UserNameContext.Provider>
  );
}
ReactDOM.createRoot(document.getElementById('try-context-adv-memo')).render(<App />);
Notes and pitfalls
- Value identity matters: avoid passing a new object each render unless it actually changed. Wrap objects in useMemo.
- Split contexts by concern (value vs dispatch, or separate fields). Consumers only re-render when the context they read changes.
- Heavy, rapidly changing data may be better managed with state libraries or fine-grained context splits.