Difficulty: Advanced
React patterns are reusable solutions for sharing logic and behavior between components. Over the years, the React community has developed several patterns, each suited to different situations. Understanding these patterns - and how they evolved - is essential for interviews and for writing maintainable React code.
Higher-Order Components (HOCs) are functions that take a component and return a new component with added behavior. The classic example is withAuth(Component) which wraps a component to check authentication before rendering. HOCs were the primary pattern for logic reuse before hooks. They work by injecting props into the wrapped component. While still used (React.memo is technically an HOC), they have downsides: prop name collisions, wrapper hell in DevTools, and static composition (cannot change which HOCs apply at runtime).
Render Props is a pattern where a component accepts a function as a prop (often called render or children) and calls that function to determine what to render. The function receives the shared state/behavior as arguments. This inverts control - the consumer decides what to render with the provided data. Render props solved HOC problems (no prop collisions, clear data flow) but introduced their own issue: callback nesting (render prop hell) when composing multiple behaviors.
Compound Components use React Context to create a group of components that work together implicitly. Think of <Select> and <Option> - Option doesn't work outside Select because Select provides the shared context. This pattern creates expressive, declarative APIs where the consumer arranges child components and the parent coordinates behavior behind the scenes.
Custom hooks are the modern standard for logic reuse. They extract stateful logic into reusable functions that any component can call. Unlike HOCs and render props, hooks compose naturally with no nesting, no prop collisions, and clear data flow. The pattern is simple: extract useState, useEffect, and other hooks into a named function starting with 'use'. Most new React code uses custom hooks for logic sharing, with compound components for UI composition.
// HOC that adds loading behavior
function withLoading(WrappedComponent) {
return function WithLoadingComponent({ isLoading, ...props }) {
if (isLoading) return <div>Loading...</div>;
return <WrappedComponent {...props} />;
};
}
// HOC that adds authentication check
function withAuth(WrappedComponent) {
return function WithAuthComponent(props) {
const { user } = useAuth();
if (!user) return <Navigate to="/login" />;
return <WrappedComponent {...props} user={user} />;
};
}
// Usage: compose HOCs
const ProtectedDashboard = withAuth(withLoading(Dashboard));
// <ProtectedDashboard isLoading={false} />
Each HOC wraps the component with additional behavior. The downside: Dashboard is now wrapped in two layers, making debugging harder in DevTools.
// Mouse position tracker using render props
function MouseTracker({ render }) {
const [position, setPosition] = React.useState({ x: 0, y: 0 });
React.useEffect(() => {
const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);
return render(position);
}
// Usage: consumer decides what to render
<MouseTracker render={({ x, y }) => (
<div>
<p>Mouse at ({x}, {y})</p>
<div style={{ position: 'absolute', left: x, top: y }}>Cursor</div>
</div>
)} />
// Alternative: children as render prop
<MouseTracker>
{({ x, y }) => <p>Position: {x}, {y}</p>}
</MouseTracker>
MouseTracker manages the logic. The consumer provides a render function that receives the position and decides the UI. This is more flexible than HOCs because the consumer has full control.
// Custom hook: replaces both the HOC and render prop patterns
function useMousePosition() {
const [position, setPosition] = React.useState({ x: 0, y: 0 });
React.useEffect(() => {
const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);
return position;
}
// Usage: simple, composable, no nesting
function CursorTracker() {
const { x, y } = useMousePosition();
const isOnline = useOnlineStatus();
const { user } = useAuth();
return (
<div>
<p>Mouse: ({x}, {y})</p>
<p>Status: {isOnline ? 'Online' : 'Offline'}</p>
<p>User: {user?.name}</p>
</div>
);
}
Custom hooks compose naturally - just call multiple hooks in sequence. No wrapper hell, no prop collisions, and data flow is explicit. This is why hooks largely replaced HOCs and render props.
higher-order components, render props, compound components, custom hooks, composition patterns