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.