React Design Patterns: A Practical Guide

The React design patterns that still matter with Hooks: composition, custom Hooks, compound components, controlled components, and the provider pattern.

8 min read

React design patterns are reusable ways to structure components and logic so an app stays readable as it grows. The patterns that matter most with Hooks are composition, custom Hooks, compound components, controlled components, and the provider pattern.

Composition

Composition means building a component from smaller pieces instead of making one component handle every case. A card receives its content through the children prop, so the card stays reusable and the caller stays in control of what renders inside.

This is the single most important pattern, and it solves most of the problems that inheritance or giant prop lists are used for. The full mechanics are in The children Prop in React. Composition also keeps data flowing one way, which is why the React docs treat it as the default over inheritance.

Custom Hooks

A custom Hook extracts stateful logic so several components can share how it works without sharing the state itself. The logic moves into a function, and each calling component gets its own independent copy.

Custom Hooks hide the details of an external system, a browser API, or a repeated piece of state. The calling code reads like intent rather than implementation.

See How to Create a Custom Hook in React for the extraction process. The rule of thumb is to extract a Hook when the logic repeats in more than one component, not for a single use.

Compound components

A compound component is a group of components that share one hidden context, so related controls work together without the caller threading props through every level. A menu, tabs, or select is the classic case: the parts know about each other, but the caller only writes JSX.

App.jsxApp.jsx
<Tabs>
  <TabList>
    <Tab>Account</Tab>
    <Tab>Billing</Tab>
  </TabList>
  <TabPanels>
    <TabPanel>Account settings</TabPanel>
    <TabPanel>Billing details</TabPanel>
  </TabPanels>
</Tabs>

Each part reads the shared tab state from context. The pattern is covered step by step in Compound Components in React.

Controlled and uncontrolled components

A component is controlled when a parent owns its value, and uncontrolled when it keeps its own state. The two differ in where the source of truth lives.

PatternData livesTypical props
ControlledParent componentvalue, onChange
UncontrolledThe component itselfdefaultValue, ref

A controlled input is the common form. The parent holds the value and updates it on every change.

App.jsxApp.jsx
import { useState } from "react";
 
function SearchBox() {
  const [query, setQuery] = useState("");
 
  return (
    <input
      aria-label="Search"
      value={query}
      onChange={(e) => setQuery(e.target.value)}
    />
  );
}

The input never decides its own text. It renders the parent's value and reports changes back, which keeps the data flow in one direction. Neither form is universally better: use controlled when the parent must read or react to the value, and uncontrolled when the value only matters inside the component.

The provider pattern

The provider pattern wraps a subtree in a context provider so distant components can read shared data without prop drilling. It is the right tool for cross-cutting values such as a theme or the current user.

App.jsxApp.jsx
import { createContext, useContext, useState } from "react";
 
const ThemeContext = createContext("light");
 
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}
 
function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);
  return (
    <button
      className={theme}
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
    >
      Toggle
    </button>
  );
}

The provider owns the state once, and any component below reads it with useContext. Context is a tool for sharing, not a replacement for props everywhere, so check whether a plain prop would be clearer before you adopt context widely.

Patterns you will still meet

Two older patterns remain common in existing code and libraries. A higher-order component is a function that wraps a component and returns a new one, used by APIs such as Redux connect. A render prop is a prop that returns JSX.

Hooks replaced both for new code, but you will read them in older codebases and in libraries like React Router. When you meet them, recognize them as the same goals as composition and custom Hooks expressed with older syntax. Reading them is still part of the job, because most long-lived React codebases contain at least one.

Rune AI

Rune AI

Key Insights

  • Composition is the foundation of every other React pattern.
  • Custom Hooks share stateful logic, not state.
  • Compound components give related controls a shared context.
  • Controlled components lift state while uncontrolled ones keep it local.
  • The provider pattern shares cross-cutting data without prop drilling.
RunePowered by Rune AI

Frequently Asked Questions

Are render props and higher-order components still used?

Yes, mostly in older code and libraries such as React Router. Hooks replaced both for new code, but you will still read and sometimes write them.

Which pattern should I learn first?

Composition. It is the foundation the other patterns build on, and it covers most of the structure problems you will meet in day to day React work.

Conclusion

The patterns that matter with Hooks are composition, custom Hooks, compound components, controlled components, and the provider pattern. Learn composition first, then add each pattern only when the specific problem it solves appears.