Advanced⏱️ 12 min📘 Topic 13 of 13

🧬 Advanced React Patterns — HOCs, Render Props & Compound Components

Senior-level React patterns: higher-order components, render props, compound components, controlled vs uncontrolled APIs, and state reducer pattern. Built for interview prep.

Senior React interviews often probe pattern fluency: when to use which, why each exists, and what hooks replaced. Here are the four that come up most.

🧰 1. Higher-Order Component (HOC)

A function that takes a component and returns a new one with extra behavior. Mostly a pre-hooks pattern.

function withAuth(Component) {
  return function Wrapped(props) {
    const { user } = useAuth();
    if (!user) return <Login />;
    return <Component {...props} user={user} />;
  };
}

🎭 2. Render Props

Pass a function as a prop (often children); the component calls it with internal state.

<Toggle>
  {({ on, toggle }) => (
    <button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>
  )}
</Toggle>

🧩 3. Compound Components

A parent shares state with closely-related children via context, exposing a flexible declarative API.

<Tabs>
  <Tabs.List>
    <Tabs.Trigger value="a">A</Tabs.Trigger>
    <Tabs.Trigger value="b">B</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Panel value="a">...</Tabs.Panel>
  <Tabs.Panel value="b">...</Tabs.Panel>
</Tabs>

Used by Radix, Reach UI, Headless UI, shadcn/ui.

🎛️ 4. Controlled vs Uncontrolled APIs (state reducer pattern)

A reusable component supports BOTH a controlled mode (parent owns state) and uncontrolled mode (internal state with optional defaultValue). The state reducer lets consumers customize update logic without forking.

💡 Hooks replaced most HOC/render-prop use cases

If you find yourself writing a new HOC today, ask whether a custom hook would be cleaner. Compound components and controlled/uncontrolled APIs remain very relevant — they're the foundation of every modern headless UI library.

💻 Code Examples

Compound Tabs (sketch)

const TabsContext = createContext(null);

export function Tabs({ defaultValue, children }) {
  const [active, setActive] = useState(defaultValue);
  return <TabsContext.Provider value={{ active, setActive }}>{children}</TabsContext.Provider>;
}
Tabs.Trigger = function Trigger({ value, children }) {
  const { active, setActive } = useContext(TabsContext);
  return <button data-active={active === value} onClick={() => setActive(value)}>{children}</button>;
};
Tabs.Panel = function Panel({ value, children }) {
  const { active } = useContext(TabsContext);
  return active === value ? <div>{children}</div> : null;
};
Output:
Consumers compose <Tabs><Tabs.Trigger /><Tabs.Panel /></Tabs> declaratively.

Render prop → equivalent custom hook

// Old (render prop)
<DataLoader url="/api/x">
  {({ data, loading }) => loading ? <Spinner /> : <View data={data} />}
</DataLoader>

// Modern (custom hook)
const { data, loading } = useFetch('/api/x');
return loading ? <Spinner /> : <View data={data} />;
Output:
Same outcome, less indirection.

⚠️ Common Mistakes

  • Reaching for HOCs in new code — usually a custom hook is clearer.
  • Overusing compound components for small widgets — adds context overhead for no gain.
  • Forgetting to memoize context values inside compound components — every keystroke re-renders all consumers.
  • Confusing 'render props' with 'function as children' — they're the same idea, just `children` happens to be the function prop.

🎯 Interview Questions

Real questions asked at top product and service-based companies.

Q1.What is a Higher-Order Component?Advanced
A function that takes a component and returns a new component with added behavior — e.g. injecting props, gating on auth, connecting to a store. It's a pre-hooks pattern; hooks usually replace HOCs today.
Q2.What is the render props pattern?Advanced
A component accepts a function as a prop (commonly `children`) and calls it with internal state. Lets you share logic without inheritance. Largely superseded by custom hooks.
Q3.What are compound components?Advanced
A set of components designed to work together via shared internal state (usually via Context). The API is declarative composition — `<Menu><Menu.Item /></Menu>` — giving consumers control over markup while the parent manages logic. Used in Radix, Headless UI, shadcn.
Q4.Why prefer compound components over a single config-prop component?Advanced
Flexibility: consumers control structure, ordering, and styling per slot. A single component with `items={[...]}` prop locks rendering decisions in the library. Compound APIs scale to complex widgets (dropdowns, tabs, dialogs) without prop explosion.
Q5.What is the controlled vs uncontrolled pattern in a library component?Advanced
Support both modes: if the parent passes `value` + `onChange`, the component is controlled; if not, it manages internal state with optional `defaultValue`. Common in inputs, switches, dialogs — gives consumers maximum flexibility.

🧠 Quick Summary

  • HOCs and render props are mostly replaced by custom hooks.
  • Compound components: declarative slots + shared context state.
  • Powers every modern headless UI lib (Radix, Headless UI, shadcn).
  • Support controlled AND uncontrolled APIs in reusable components.
  • State reducer pattern lets consumers customize update logic.