Render props pattern in React
What is the Render Props Pattern?
Render props is a pattern where a component receives a function as a prop that returns React elements. This allows the component to share its internal logic/state while letting the consumer control the rendering.
Basic Example
tsx
// Component with render prop — shares mouse position
function MouseTracker({ render }: {
render: (position: { x: number; y: number }) => React.ReactNode;
}) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e: React.MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div onMouseMove={handleMouseMove} style={{ height: "100vh" }}>
{render(position)}
</div>
);
}
// Consumer controls the UI
function App() {
return (
<MouseTracker
render={({ x, y }) => (
<p>Mouse is at ({x}, {y})</p>
)}
/>
);
}Using children as Render Prop
tsx
function MouseTracker({ children }: {
children: (position: { x: number; y: number }) => React.ReactNode;
}) {
const [position, setPosition] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={e => setPosition({ x: e.clientX, y: e.clientY })}>
{children(position)}
</div>
);
}
// Usage — cleaner syntax
<MouseTracker>
{({ x, y }) => <span>({x}, {y})</span>}
</MouseTracker>Practical Examples
Toggle Component
tsx
function Toggle({ children }: {
children: (props: { isOn: boolean; toggle: () => void }) => React.ReactNode;
}) {
const [isOn, setIsOn] = useState(false);
const toggle = () => setIsOn(prev => !prev);
return <>{children({ isOn, toggle })}</>;
}
// Reusable toggle logic, different UIs
<Toggle>
{({ isOn, toggle }) => (
<button onClick={toggle}>
{isOn ? "ON" : "OFF"}
</button>
)}
</Toggle>
<Toggle>
{({ isOn, toggle }) => (
<div className={`switch ${isOn ? "active" : ""}`} onClick={toggle} />
)}
</Toggle>Data Fetcher
tsx
function DataFetcher<T>({ url, children }: {
url: string;
children: (props: { data: T | null; loading: boolean; error: string | null }) => React.ReactNode;
}) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(e => setError(e.message))
.finally(() => setLoading(false));
}, [url]);
return <>{children({ data, loading, error })}</>;
}
// Usage
<DataFetcher<User[]> url="/api/users">
{({ data, loading, error }) => {
if (loading) return <Spinner />;
if (error) return <Error message={error} />;
return <UserList users={data!} />;
}}
</DataFetcher>Render Props vs Custom Hooks
| Feature | Render Props | Custom Hooks |
|---|---|---|
| Reuse logic | ✅ | ✅ |
| No wrapper component | ❌ (adds wrapper) | ✅ |
| Works in class components | ✅ | ❌ |
| Can control rendering | ✅ | ❌ |
| Readability | Can become nested | Cleaner |
tsx
// The same logic as a Custom Hook (modern approach)
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handler = (e: MouseEvent) => setPosition({ x: e.clientX, y: e.clientY });
window.addEventListener("mousemove", handler);
return () => window.removeEventListener("mousemove", handler);
}, []);
return position;
}
// Usage — cleaner, no wrapper
function App() {
const { x, y } = useMousePosition();
return <p>({x}, {y})</p>;
}Important:
Render props is a powerful pattern for sharing stateful logic between components. While custom hooks have largely replaced render props in modern React, the pattern is still valuable in libraries (e.g., React Router, Formik, Downshift) and when you need components that work with both class and functional components.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.