Render Props Pattern
What Are Render Props?
A component that accepts a function as a prop and calls it to determine what to render — giving the consumer complete control over rendering while the provider supplies data and behavior.
// Provider: supplies mouse position
function MouseTracker({ render }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={e => setPos({ x: e.clientX, y: e.clientY })}>
{render(pos)} {/* Consumer decides what to render */}
</div>
);
}
// Consumer: decides what to render
<MouseTracker render={({ x, y }) => (
<h1>Mouse at {x}, {y}</h1>
)} />
Three Implementation Styles
// 1. Named "render" prop
<DataProvider render={(data) => <Display data={data} />} />
// 2. Children as function (most common)
<DataProvider>
{(data) => <Display data={data} />}
</DataProvider>
// 3. Any named prop
<DataProvider content={(data) => <Display data={data} />} />
Real-World Examples
Reusable Data Fetcher
function DataFetcher({ url, children }) {
const { data, loading, error } = useFetch(url);
return children({ data, loading, error });
}
<DataFetcher url="/api/products">
{({ data: products, loading, error }) => {
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <ProductGrid products={products} />;
}}
</DataFetcher>
Intersection Observer (Lazy Loading)
function IntersectionObserver({ children, threshold = 0.1 }) {
const [isVisible, setVisible] = useState(false);
const ref = useRef(null);
useEffect(() => {
const observer = new window.IntersectionObserver(
([entry]) => setVisible(entry.isIntersecting),
{ threshold }
);
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, [threshold]);
return <div ref={ref}>{children({ isVisible, ref })}</div>;
}
<IntersectionObserver>
{({ isVisible }) => isVisible ? <HeavyChart /> : <Placeholder />}
</IntersectionObserver>
Performance Optimization
Inline functions create new references every render — breaking memoization:
// ❌ New function every render — breaks React.memo in children
<Provider render={(data) => <Display data={data} />} />
// ✅ Stable reference
const renderDisplay = useCallback(
(data) => <Display data={data} />,
[]
);
<Provider render={renderDisplay} />
Render Props vs Hooks vs HOCs
| Scenario | Best Choice |
|---|---|
| Library APIs needing flexible rendering | Render Props |
| Application logic in function components | Custom Hooks |
| Legacy/class component enhancement | HOC or Render Props |
| Animation/virtualization needing render control | Render Props |
| Data fetching in app code | Custom Hooks |
| Cross-cutting concerns (analytics, feature flags) | HOC or Hooks |
2024 default: Hooks for almost everything. Render props for component libraries where the API needs to be flexible about what gets rendered.
Common Mistakes
// ❌ Creating new component type inside render prop
<Provider render={(data) => {
function InlineComponent() { return <div>{data}</div>; }
return <InlineComponent />; // New component type each render!
}} />
// ✅ Define outside or use JSX directly
<Provider render={(data) => <div>{data}</div>} />
// ❌ Deeply nested render props (callback hell)
<AuthProvider render={auth =>
<DataProvider render={data =>
<UIProvider render={ui =>
<Dashboard auth={auth} data={data} ui={ui} />
} />
} />
} />
// ✅ Use hooks or compound components instead
function Dashboard() {
const auth = useAuth();
const data = useData();
const ui = useUI();
return <DashboardContent auth={auth} data={data} ui={ui} />;
}
Content from Frontend-Master-Prep-Series — 03-react