Advanced React Patterns: Render Props and Higher-Order Components
As React applications grow in complexity, understanding advanced patterns becomes crucial for building maintainable, reusable components. Render props and higher-order components are powerful techniques that enable flexible component composition.
Render Props Pattern
The render props pattern allows components to share code by passing a function as a prop that returns JSX.
Basic Implementation
function MouseTracker({ render }) {
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setMousePosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return render(mousePosition);
}
// Usage
<MouseTracker render={(mousePosition) => (
<div>
Mouse is at: {mousePosition.x}, {mousePosition.y}
</div>
)} />
Render Props with Hooks
Modern React with hooks provides an even cleaner approach:
function useMousePosition() {
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setMousePosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return mousePosition;
}
// Usage
function MouseDisplay() {
const mousePosition = useMousePosition();
return (
<div>
Mouse position: {mousePosition.x}, {mousePosition.y}
</div>
);
}
Higher-Order Components (HOCs)
HOCs are functions that take a component and return a new component with additional props or behavior.
Basic HOC Pattern
function withLoading(WrappedComponent) {
return function WithLoadingComponent({ isLoading, ...props }) {
if (isLoading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...props} />;
};
}
// Usage
const UserProfileWithLoading = withLoading(UserProfile);
// In your component
<UserProfileWithLoading
isLoading={loading}
user={user}
onUpdate={handleUpdate}
/>
HOC with Hooks
function withUserData(WrappedComponent) {
return function WithUserDataComponent(props) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser().then(user => {
setUser(user);
setLoading(false);
});
}, []);
return (
<WrappedComponent
{...props}
user={user}
loading={loading}
/>
);
};
}
Compound Components
Compound components allow related components to work together while maintaining a clean API.
Basic Compound Component
function Menu({ children }) {
return <div className="menu">{children}</div>;
}
function MenuItem({ children, onClick }) {
return (
<div className="menu-item" onClick={onClick}>
{children}
</div>
);
}
// Usage
<Menu>
<MenuItem onClick={() => console.log('Home')}>Home</MenuItem>
<MenuItem onClick={() => console.log('About')}>About</MenuItem>
<MenuItem onClick={() => console.log('Contact')}>Contact</MenuItem>
</Menu>
Advanced Compound Component with Context
const MenuContext = createContext();
function Menu({ children }) {
const [activeItem, setActiveItem] = useState(null);
return (
<MenuContext.Provider value={{ activeItem, setActiveItem }}>
<div className="menu">{children}</div>
</MenuContext.Provider>
);
}
function MenuItem({ children, itemId, onClick }) {
const { activeItem, setActiveItem } = useContext(MenuContext);
const isActive = activeItem === itemId;
return (
<div
className={`menu-item ${isActive ? 'active' : ''}`}
onClick={() => {
setActiveItem(itemId);
onClick?.();
}}
>
{children}
</div>
);
}
Best Practices
When to Use Each Pattern
- Render Props: When you need to share logic that involves rendering decisions
- HOCs: When you need to add the same props or behavior to multiple components
- Compound Components: When building related components that should work together
Common Pitfalls
- HOC naming collisions: Always use displayName for debugging
- Render props callback hell: Keep render props simple and focused
- Compound component complexity: Don't over-engineer simple component relationships
Modern Alternatives
With React 18 and the rise of hooks, some patterns have evolved:
Custom Hooks as an Alternative
function useMenuState() {
const [activeItem, setActiveItem] = useState(null);
return { activeItem, setActiveItem };
}
function Menu({ children, onItemClick }) {
const { activeItem, setActiveItem } = useMenuState();
return (
<div className="menu">
{React.Children.map(children, child =>
React.cloneElement(child, {
activeItem,
setActiveItem,
onClick: () => {
setActiveItem(child.props.itemId);
onItemClick?.(child.props.itemId);
}
})
)}
</div>
);
}
Conclusion
Advanced React patterns like render props, HOCs, and compound components enable powerful component composition and code reuse. While hooks have simplified many use cases, these patterns remain valuable tools for complex component architectures.
The key is choosing the right pattern for your specific use case and maintaining clean, predictable APIs that other developers can easily understand and use.
Tags: