Frontend Master

JavaScript Curriculum

Component Patterns
+60 XP

Component Patterns

hard
~30 min·60 XP

The coffee shop UI needs protected routes (HOC), a tabbed menu (compound components), and a reusable rating widget (controlled component). Each pattern solves a different composition problem.

Component Patterns

These patterns are design solutions to recurring problems in React architecture. Knowing them helps you read any codebase and choose the right tool for each situation.

Four Patterns — Controlled, HOC, Render Props, Compound

When to use each and how they compare:

advanced-patterns.jsx

The parent fully controls the component's value via props. The component calls a callback to request changes — it owns no state of its own. The most common and recommended pattern in React.

// ✓ Controlled — parent owns value
function RatingInput({ value, onChange }) {
  return (
    <div className="rating">
      {[1, 2, 3, 4, 5].map(star => (
        <button
          key={star}
          onClick={() => onChange(star)}
          className={star <= value ? 'star filled' : 'star'}
        ></button>
      ))}
    </div>
  )
}

// Parent controls value:
function ReviewForm() {
  const [rating, setRating] = useState(0)
  return <RatingInput value={rating} onChange={setRating} />
}
Live demo
value={rating} = 3
Parent owns `rating` state. RatingInput only calls onChange — it has no state of its own.
ℹ️Controlled components are the default
A controlled component has no internal state — the parent fully owns the value. This is the React way for any input or interactive component: value prop in, onChange callback out. The parent always knows the current state.

HOC vs Custom Hooks — The Modern Verdict

Why hooks replaced HOCs for logic sharing:

hoc-vs-hooks.jsx
// ✓ Custom hooks approach — flat and composable
function Dashboard() {
  const user    = useAuth()       // replaces withAuth
  const logging = useLogging()    // replaces withLogging
  // ErrorBoundary still as component (can't hook that)

  if (!user) return <Navigate to="/login" />

  return <div>{user.name}'s Dashboard</div>
}

// Hooks are just function calls — no nesting:
function useAuth() {
  const user = useSelector(state => state.user)
  return user  // null if not logged in
}
function useLogging() {
  const name = useRef(null)
  useEffect(() => { logRender(name.current) }, [])
}

// Advantages over HOC:
// ✓ No wrapper nesting
// ✓ Explicit — you can see all dependencies
// ✓ Easy to test in isolation
// ✓ No prop collisions
// ✓ Works with TypeScript naturally
Comparison
Readability
Flat — explicit function calls
Debugging
Easy — hooks are visible in source
TypeScript
Natural type inference
Testing
Test hook directly with renderHook
Prop clashes
None — hooks don't inject props
Use today
✓ Preferred in modern React
💡Use hooks, not HOCs, for logic sharing
When you find yourself writing a HOC, ask: can this be a custom hook instead? Almost always yes. Hooks are simpler, more explicit, have no wrapper nesting, and work naturally with TypeScript. Reserve HOCs for third-party integrations that require the pattern.

Compound Components — Live Demo

A working Tabs component built with the compound pattern:

compound-components.jsx

Live compound Tabs component — Tabs, TabList, Tab, and TabPanel all share state via Context. No props passed between siblings.

Espresso
£2.50

A concentrated shot of coffee, rich and intense.

Tab and TabPanel share active state via TabsContext — no props passed through TabList or the component tree.
⚠️Compound components need Context
The sub-components (Tab, Panel) need to share state with the parent (Tabs) without explicit prop passing. Context is the right tool here — it is used intentionally, not to avoid prop drilling, but as the communication channel between tightly coupled sub-components.

Your Challenge

Build Tabs with Context. Attach Tabs.List, Tabs.Tab, Tabs.Panel as properties. Tab should highlight when active === id. Panel should only render when active === id. Build a controlled StarRating that accepts value and onChange. Replace a hypothetical withAuth(Component) HOC with a useAuth() hook.

Challenge

Build a compound Tabs component with Tabs, Tabs.List, Tabs.Tab, Tabs.Panel. Use Context for shared state. Build a controlled StarRating component. Replace a legacy withAuth HOC with a useAuth custom hook.

controlled-componentHOChigher-order-componentrender-propscompound-componentsContextcustom-hookswithAuthTabs-pattern
Component Patterns | Nexus Learn