Technology · React
React Composition
Master React patterns like compound components, render props, and higher-order components for clean reusable patterns.
TL;DR
- 01Use compound components to create flexible component APIs.
- 02Use render props to share logic through children functions.
- 03Use higher-order components to wrap and enhance existing components.
Compound Components
- Build components that work together as a group with shared state.
const Accordion = ({ children }) => { const [active, setActive] = useState(null); return ( <AccordionContext.Provider value={{ active, setActive }}> {children} </AccordionContext.Provider> ); }; const AccordionItem = ({ id, title, children }) => { const { active, setActive } = useContext(AccordionContext); return ( <div> <button onClick={() => setActive(id)}>{title}</button> {active === id && <div>{children}</div>} </div> ); }; <Accordion> <AccordionItem id="1" title="Section 1">Content 1</AccordionItem> <AccordionItem id="2" title="Section 2">Content 2</AccordionItem> </Accordion> - Compound components are flexible because the child components control their appearance.
- They share state through Context instead of passing props through every level.
- Use this pattern for tightly coupled components like form fields and labels.
Render Props
- Pass a function as a prop to let child components decide what to render.
const MouseTracker = ({ render }) => { const [pos, setPos] = useState({ x: 0, y: 0 }); const handleMouseMove = (e) => { setPos({ x: e.clientX, y: e.clientY }); }; return ( <div onMouseMove={handleMouseMove}> {render(pos)} </div> ); }; <MouseTracker render={({ x, y }) => ( <p>Mouse at {x}, {y}</p> )} /> - The render function receives data and returns JSX to display.
- This lets you reuse logic without creating a wrapper component.
- Render props are flexible because the caller decides what to render.
- The
childrenfunction is a special render prop pattern.<MouseTracker> {({ x, y }) => <p>Position: {x}, {y}</p>} </MouseTracker>
Higher-Order Components (HOC)
- Wrap a component to add or modify its behavior.
const withAuth = (Component) => { return (props) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { checkAuth().then(u => { setUser(u); setLoading(false); }); }, []); if (loading) return <p>Loading...</p>; if (!user) return <p>Not authenticated</p>; return <Component user={user} {...props} />; }; }; const Dashboard = ({ user }) => <h1>Welcome {user.name}</h1>; const ProtectedDashboard = withAuth(Dashboard); - HOCs add props, logic, or wrappers to existing components.
- They're useful for cross-cutting concerns like authentication or theming.
- Avoid deeply nested HOCs — use composition instead.
When to Use Each Pattern
- Use compound components when building a cohesive component suite.
<Form> <Form.Field name="email" /> <Form.Field name="password" /> <Form.Submit>Login</Form.Submit> </Form> - Use render props to share logic between unrelated components.
<DataFetcher url="/api/users" render={data => ( <UserList users={data} /> )} /> - Use HOCs to enhance existing components with logic.
const enhancedComponent = withDataFetching(MyComponent); - Prefer composition over deeply nested patterns for readability.
Modern Alternatives
- Custom hooks often replace render props and HOCs for sharing logic.
function useMousePosition() { const [pos, setPos] = useState({ x: 0, y: 0 }); useEffect(() => { const handleMove = (e) => setPos({ x: e.clientX, y: e.clientY }); window.addEventListener('mousemove', handleMove); return () => window.removeEventListener('mousemove', handleMove); }, []); return pos; } function MyComponent() { const pos = useMousePosition(); return <p>Position: {pos.x}, {pos.y}</p>; } - Custom hooks are simpler and more flexible than render props or HOCs.
- Use hooks as your first choice for sharing logic in modern React.
- Composition patterns are still useful for component APIs and layout.
Tip: Use custom hooks instead of render props or HOCs for new code, since hooks are simpler and easier to understand.
Warning: Deeply nested HOCs create hard-to-debug code — prefer flat composition using multiple custom hooks instead.