Note The default value is used only when a component reads the context but there is no matching Provider above it in the tree. Pass a realistic shape as the default so TypeScript inference and fallback behavior work correctly.
Note Wrap the Provider at the highest point where descendant components need the value. If the value is a new object on every render, wrap it with useMemo to prevent unnecessary re-renders of consumers.
provide contextcontext providerwrap with providerpass context value
Consuming Context
syntax
import { useContext } from'react';
const value = useContext(MyContext);
Note useContext always looks for the nearest Provider above the calling component. If none is found, it uses the default value from createContext. The component re-renders whenever the provided value changes.
useContextconsume contextread contextaccess context value
Note Split unrelated data into separate contexts. A single monolithic context forces every consumer to re-render when any part of the value changes, even if they only use a small slice.
// Encapsulate context + hook in a moduleconst Ctx = createContext(null);
exportfunction useMyContext() {
const ctx = useContext(Ctx);
if (!ctx) thrownew Error('Missing Provider');
return ctx;
}
example
import { createContext, useContext, useState } from'react';
const CartContext = createContext(null);
exportfunction CartProvider({ children }) {
const [items, setItems] = useState([]);
function addItem(product) {
setItems(prev => [...prev, { ...product, qty: 1 }]);
}
function removeItem(id) {
setItems(prev => prev.filter(item => item.id !== id));
}
const totalItems = items.reduce((sum, i) => sum + i.qty, 0);
return (
<CartContext.Provider value={{ items, addItem, removeItem, totalItems }}>
{children}
</CartContext.Provider>
);
}
exportfunction useCart() {
const context = useContext(CartContext);
if (!context) {
thrownew Error('useCart must be used within a CartProvider');
}
return context;
}
Note Exporting a custom hook that throws when the Provider is missing catches mistakes early and gives a clear error message instead of undefined values that silently break downstream.
context custom hookcontext with stateprovider patterncart contextglobal state context
Avoiding Unnecessary Re-renders
syntax
// Memoize the value objectconst value = useMemo(() => ({ data, handler }), [data]);
// Split read-only data from dispatch
<DataContext.Provider value={data}>
<DispatchContext.Provider value={dispatch}>
Note Splitting context into a data context and a dispatch context is a powerful pattern. Components that only call actions (dispatch) will not re-render when the data changes, because the dispatch reference stays stable.