NetcaneTechnologies

React

Reusable UI Patterns: Compound Components vs Render Props vs Hooks

A practical comparison of three ways to build flexible, reusable UI in React—and when to pick each.

Yasir Haleem4 min read

Choosing how to expose flexibility in React components affects both your API and your bundle. Compound components, render props, and custom hooks each solve different problems. Here’s when and how to use them.

Compound components: shared state, flexible layout

Compound components let you keep state in a parent while giving the consumer full control over structure and order. The parent provides context; children consume it and render. No prop drilling, and the markup reads like a small DSL.

const TabsContext = createContext(null);
 
export function Tabs({ defaultValue, children }) {
  const [active, setActive] = useState(defaultValue);
  return (
    <TabsContext.Provider value={{ active, setActive }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}
 
Tabs.List = function TabsList({ children }) {
  return <div role="tablist">{children}</div>;
};
 
Tabs.Trigger = function TabsTrigger({ value, children }) {
  const { active, setActive } = useContext(TabsContext);
  return (
    <button
      role="tab"
      aria-selected={active === value}
      onClick={() => setActive(value)}
    >
      {children}
    </button>
  );
};
 
Tabs.Panel = function TabsPanel({ value, children }) {
  const { active } = useContext(TabsContext);
  if (active !== value) return null;
  return <div role="tabpanel">{children}</div>;
};

Usage stays declarative: <Tabs><Tabs.List>...</Tabs.List></Tabs>. You can reorder or wrap parts without changing the component’s internals. Use compound components when the same state must drive multiple pieces of UI and you want layout flexibility.

Render props: one-off flexibility

Render props pass a function as children (or another prop) and call it with data or callbacks. The consumer decides exactly what to render. No extra components to document; the contract is “here’s data, you render.”

function MouseTracker({ render }) {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const handler = (e) => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener("mousemove", handler);
    return () => window.removeEventListener("mousemove", handler);
  }, []);
  return render(pos);
}
 
// Usage
<MouseTracker render={({ x, y }) => <span>Cursor: {x}, {y}</span>} />

The downside is nesting and less readable JSX when you compose several such components. Render props shine for one-off behavior (tracking, visibility, data fetching) where you don’t need a full component API.

Custom hooks: logic reuse without UI

Hooks extract state and side effects. They don’t dictate UI; the component does. That makes them ideal for reuse across different UIs (dropdowns, modals, lists) that share the same behavior.

function useDisclosure(defaultOpen = false) {
  const [isOpen, setIsOpen] = useState(defaultOpen);
  const open = useCallback(() => setIsOpen(true), []);
  const close = useCallback(() => setIsOpen(false), []);
  const toggle = useCallback(() => setIsOpen((p) => !p), []);
  return { isOpen, open, close, toggle };
}
 
// In a modal
const { isOpen, open, close } = useDisclosure();
// In a dropdown
const { isOpen, toggle } = useDisclosure();

Same logic, different components. Use hooks when the reuse is about behavior, not layout. Combine them with compound components when you want both: a hook for the logic, compound components for the UI shell.

When to use which

Use compound components when you’re building a feature with multiple coordinated pieces (tabs, accordions, menus) and you want a clear, flexible API. Use render props when you need a single injection point and don’t want to maintain a set of subcomponents. Use hooks when the valuable part is state and effects, and the UI should stay in the caller’s hands.

You can mix them: a useTabs hook for state, plus Tabs, Tabs.List, Tabs.Trigger, and Tabs.Panel for the compound API. That keeps logic testable and the public API easy to use.

Summary

Compound components give structure and shared state; render props give one-off rendering control; hooks give reusable logic. Pick compound components for rich, multi-part UI; render props for simple “here’s data, you render”; hooks for behavior you’ll reuse across many UIs.

About the author

Yasir Haleem is founder and lead engineer at Netcane Technologies. He builds production Next.js sites with headless CMS platforms — Strapi, Contentful, Sanity, and WordPress — with a focus on performance, SEO, and maintainable architecture.

Let's work together

Tell us about your project. We respond within one business day with a clear scope, timeline, and estimate.