Dung (Donny) Nguyen

Senior Software Engineer

Advanced Patterns

As React applications grow, we often need ways to share logic between components, keep components flexible, and design APIs that are pleasant to use. Advanced patterns are proven techniques for solving these reuse and composition problems. The three classic patterns every React developer should know are Higher-Order Components (HOCs), Render Props, and Compound Components.

Modern React largely favors custom hooks for logic reuse, but these patterns are still widely used in libraries and existing codebases, and each shines in specific situations.

Higher-Order Components (HOCs)

A Higher-Order Component is a function that takes a component and returns a new, enhanced component. It is React’s version of the higher-order function concept from JavaScript.

// A HOC that injects the current window width as a prop
import { useEffect, useState } from 'react';

function withWindowWidth(WrappedComponent) {
  return function WithWindowWidth(props) {
    const [width, setWidth] = useState(window.innerWidth);

    useEffect(() => {
      const handleResize = () => setWidth(window.innerWidth);
      window.addEventListener('resize', handleResize);
      return () => window.removeEventListener('resize', handleResize);
    }, []);

    return <WrappedComponent {...props} windowWidth={width} />;
  };
}

// Usage
function Banner({ windowWidth }) {
  return <p>Window is {windowWidth}px wide.</p>;
}

export default withWindowWidth(Banner);

When to use HOCs:

Drawbacks:

Render Props

A render prop is a prop whose value is a function that returns JSX. The component calls this function to let the consumer decide what to render, while it manages the underlying logic and state.

import { useState } from 'react';

function MousePosition({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };

  return (
    <div style= onMouseMove={handleMouseMove}>
      {render(position)}
    </div>
  );
}

// Usage
function App() {
  return (
    <MousePosition
      render={({ x, y }) => (
        <h1>The mouse is at ({x}, {y})</h1>
      )}
    />
  );
}

The children prop is often used as the render function too:

<MousePosition>
  {({ x, y }) => <h1>({x}, {y})</h1>}
</MousePosition>

When to use render props:

Drawbacks:

Compound Components

Compound components let multiple components work together to form a single, cohesive unit. The parent manages shared state and communicates with its children implicitly — usually through the Context API. Think of <select> and <option> in HTML: they only make sense together.

import { createContext, useContext, useState } from 'react';

const TabsContext = createContext();

function Tabs({ children, defaultIndex = 0 }) {
  const [activeIndex, setActiveIndex] = useState(defaultIndex);
  return (
    <TabsContext.Provider value=>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

function TabList({ children }) {
  return <div className="tab-list">{children}</div>;
}

function Tab({ index, children }) {
  const { activeIndex, setActiveIndex } = useContext(TabsContext);
  const isActive = index === activeIndex;
  return (
    <button
      className={isActive ? 'tab active' : 'tab'}
      onClick={() => setActiveIndex(index)}
    >
      {children}
    </button>
  );
}

function TabPanel({ index, children }) {
  const { activeIndex } = useContext(TabsContext);
  return index === activeIndex ? <div className="tab-panel">{children}</div> : null;
}

// Attach children to the parent for a clean API
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;

// Usage
function App() {
  return (
    <Tabs defaultIndex={0}>
      <Tabs.List>
        <Tabs.Tab index={0}>Profile</Tabs.Tab>
        <Tabs.Tab index={1}>Settings</Tabs.Tab>
      </Tabs.List>
      <Tabs.Panel index={0}>Profile content</Tabs.Panel>
      <Tabs.Panel index={1}>Settings content</Tabs.Panel>
    </Tabs>
  );
}

When to use compound components:

Drawbacks:

Custom Hooks: The Modern Default

Since Hooks arrived, most logic-reuse problems that once required HOCs or render props are now solved more cleanly with custom hooks. A custom hook is just a function that starts with use and calls other hooks.

import { useEffect, useState } from 'react';

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return width;
}

// Usage — no wrappers, no nesting
function Banner() {
  const width = useWindowWidth();
  return <p>Window is {width}px wide.</p>;
}

Custom hooks avoid wrapper nesting entirely and make data sources explicit, which is why they are the recommended approach for sharing logic. Compound components remain the go-to pattern for sharing UI structure.

Choosing the Right Pattern

Pattern Best For Modern Status
HOC Cross-cutting concerns, legacy library APIs Largely replaced by hooks
Render Props Sharing behavior with flexible rendering Largely replaced by hooks
Compound Components Cohesive, flexible UI component APIs Still recommended
Custom Hooks Reusing stateful logic Preferred default

Summary

Advanced patterns give us the vocabulary and tools to build reusable, maintainable React components. HOCs and render props were the original solutions for sharing logic, but custom hooks now handle most of those cases with less nesting and clearer intent. Compound components remain the best choice for designing flexible, declarative UI components. Understanding all four helps us read existing codebases, use popular libraries effectively, and design better APIs of our own.