I'll never forget the moment when I had to pass the user's authentication state through seven levels of components just so a "Logout" button could access it. The App component had auth state, which went to Layout, then Sidebar, then UserMenu, then MenuDropdown, then DropdownList, and finally to LogoutButton. Six components that didn't care about auth, just passing it along like a relay race. That's when I learned about Context.
Context is React's built-in solution for sharing state globally without passing props through every level. It's perfect for data that many components need: theme settings, user authentication, shopping cart, language preferences. In the POS system, I use Context for cart state and user authenticationβdata that needs to be accessible anywhere in the app.
When to Use Context vs Props
Use props when:
Data is only needed by direct children
The component hierarchy is shallow (1-2 levels)
You want explicit data flow
Use Context when:
Many components at different levels need the same data
You're passing props through components that don't use them (prop drilling)
The data is truly global (auth, theme, cart)
In the POS system:
Product data β Props (only needed by product components)
User authentication β Context (needed everywhere)
Cart state β Context (needed by many components)
Individual product quantity β Props (local to that component)
Creating Your First Context: Cart
Let's solve the cart prop drilling problem with Context.
Step 1: Create the Context
What's happening here?
Create context: createContext<CartContextType>()
Provider component: Manages state and provides value
Custom hook: Makes using context easier and safer
Step 2: Wrap Your App with the Provider
Everything inside <CartProvider> can now access cart state.
Step 3: Use the Context in Any Component
No more prop drilling! Any component can access cart state with useCart().
Creating Authentication Context
Authentication is another perfect use case for Context. Let's build it for the POS system.
Using Auth Context
Composing Multiple Contexts
Real applications need multiple contexts. The pattern:
Nesting can get deep. Here's a cleaner pattern:
Context with Local Storage Persistence
Let's make the cart persist across page refreshes:
Now the cart survives page refreshes!
Real POS Example: Complete Context Setup
Here's the production-quality setup I use in the POS system:
Using it across the app:
When NOT to Use Context
Context isn't always the answer. Don't use Context for:
1. Frequently Changing Values
Better: Keep fast-changing state local or use a state management library.
2. Simple Parent-Child Communication
Better: Pass props directly.
3. Complex State Logic
Better: Split into multiple contexts or use a state management library.
Common Mistakes to Avoid
1. Creating Context Without Provider
Fix: Always wrap with provider.
2. Not Checking for Undefined
Fix: Check and throw error:
3. Creating New Objects Every Render
Fix: Use useMemo or define functions outside render:
4. Too Many Contexts
Fix: Combine related contexts or use a state management library for complex apps.
Key Learnings
Context solves prop drilling for global state
Use Context for: auth, theme, cart, language
Don't use Context for: frequent updates, simple parent-child communication
Always provide a custom hook (useCart) for type safety
Wrap app with Provider to make context available
Check for undefined in custom hooks
Combine multiple contexts by nesting providers
Persist state with localStorage + useEffect
Next Steps
You now know how to share state globally with Context. But Context provides values, not reusable logic. What if you want to extract data fetching logic or cart operations into reusable functions? That's where custom hooks come in. In the next article, we'll build useProducts, useCart, and useAuth hooks to organize our POS system's logic.
// β Bad: Every keystroke re-renders all consumers
function SearchProvider({ children }) {
const [searchTerm, setSearchTerm] = useState('');
// All components using this context re-render on every keystroke!
}
// β Overkill: Just use props
function ParentComponent() {
return (
<ThemeProvider> {/* Unnecessary! */}
<ChildComponent />
</ThemeProvider>
);
}
// β Context getting too complex
const value = {
users, setUsers,
products, setProducts,
orders, setOrders,
// ... 20 more values
};
// β Using context without provider
function App() {
return <ProductCard />; // useCart() will fail!
}
export function useCart() {
const context = useContext(CartContext);
// β Might be undefined if used outside provider
return context;
}
export function useCart() {
const context = useContext(CartContext);
if (context === undefined) {
throw new Error('useCart must be used within CartProvider');
}
return context;
}
function CartProvider({ children }) {
const [cart, setCart] = useState([]);
// β New object every render - all consumers re-render!
const value = {
cart,
addToCart: () => {},
removeFromCart: () => {}
};
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}