Learn / Frameworks / React / Component Patterns

React · Lesson 14 of 15

Component Patterns

Compound components, portals, forwarding refs and composition that scales.

  • Advanced
  • 16 min read
  • 3 objectives

Before this lessonLesson 13: App-wide State with Zustand

What you will learn

  • Build a compound component
  • Use a portal
  • Forward a ref

Your Progress

0 of 15 lessons 0%

  • Lessons0 / 15
  • Completed0
  • Est. time left~ 4 hours

Create a free account to keep your progress on every device.

As a UI kit grows, a few composition patterns keep components flexible without a props explosion.

Compound components

Related pieces share state through context, and the parent decides the markup.

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

const TabsCtx = createContext(null);

export function Tabs({ children, defaultValue }) {
  const [value, setValue] = useState(defaultValue);
  return <TabsCtx.Provider value={{ value, setValue }}>{children}</TabsCtx.Provider>;
}
Tabs.List = function List({ children }) { return <div role="tablist">{children}</div>; };
Tabs.Tab = function Tab({ id, children }) {
  const { value, setValue } = useContext(TabsCtx);
  return (
    <button role="tab" aria-selected={value === id} onClick={() => setValue(id)}>
      {children}
    </button>
  );
};
Tabs.Panel = function Panel({ id, children }) {
  const { value } = useContext(TabsCtx);
  if (value !== id) return null;
  return <div role="tabpanel">{children}</div>;
};

Portals

Modals and toasts should escape overflow and stacking-context traps. createPortal renders children into a different DOM node.

import { createPortal } from "react-dom";

function Modal({ children, onClose }) {
  return createPortal(
    <div className="overlay" onClick={onClose}>
      <div className="dialog" onClick={(e) => e.stopPropagation()}>{children}</div>
    </div>,
    document.body,
  );
}

Forwarding refs

A custom input that wraps <input> must forward the ref so a parent can focus it.

import { forwardRef } from "react";

const TextField = forwardRef(function TextField({ label, ...props }, ref) {
  return (
    <label>
      {label}
      <input ref={ref} {...props} />
    </label>
  );
});
Up next · Lesson 15Accessibility, Build and DeployShip an accessible production build with env vars and a static host.