Skip to Content
FrontendReactAdvance3.5 Advanced Component APIs

Advanced Component APIs 🧱

Go beyond basic props by exposing refs, composing child APIs, and delivering headless components that scale into reusable libraries.

1) forwardRef 🔗

  • Allows parent components to pass refs into functional children.
import { forwardRef } from "react"; const Input = forwardRef(function Input(props, ref) { return <input ref={ref} {...props} />; }); function Form() { const inputRef = useRef(null); return <Input ref={inputRef} placeholder="Focus me" />; }
  • Needed for components that wrap DOM nodes but still expose focus/measure APIs.

2) useImperativeHandle 🕹️

Customize what ref consumers can access.

const Expander = forwardRef(function Expander(props, ref) { const [open, setOpen] = useState(false); useImperativeHandle(ref, () => ({ toggle: () => setOpen(prev => !prev) })); return ( <section> <button onClick={() => setOpen(prev => !prev)}>Toggle</button> {open && <div>{props.children}</div>} </section> ); });
  • Expose minimal imperative API (focus, reset, validate) instead of entire component internals.

3) Compound Components Pattern 🧩

  • Parent component coordinates state; children consume context to stay synchronized.
const TabsContext = createContext(null); function Tabs({ children }) { const [active, setActive] = useState("0"); const value = { active, setActive }; return <TabsContext.Provider value={value}>{children}</TabsContext.Provider>; } function TabList({ children }) { return <div role="tablist">{children}</div>; } function Tab({ id, children }) { const { active, setActive } = useContext(TabsContext); const isActive = active === id; return ( <button role="tab" aria-selected={isActive} onClick={() => setActive(id)}> {children} </button> ); }
  • Consumers compose APIs declaratively: <Tabs><TabList>...</TabList><TabPanels>...</TabPanels></Tabs>.

4) Headless Components 🧠

  • Provide logic + state but leave rendering to the consumer.
  • Example: headless dropdown hook returning props/state; consumer renders markup + styles.
function useDisclosure(initial = false) { const [open, setOpen] = useState(initial); const toggle = () => setOpen(prev => !prev); return { open, toggle }; }
  • Headless UI (by Tailwind Labs) exemplifies this pattern.

5) Building Reusable Libraries 🧱

Checklist:

  1. API design: stable prop names, clear defaults.
  2. Accessibility: ARIA roles, keyboard interactions, focus traps.
  3. Theming: expose className/style overrides or render props.
  4. Documentation: MDX stories, prop tables, examples.
  5. Testing: unit + visual regression to prevent breaking changes.

🧠 Think consumer-first: components should be composable, themeable, and predictable.

Key Takeaways ✅

  • forwardRef and useImperativeHandle expose controlled imperative surfaces.
  • Compound components coordinate shared state while keeping markup flexible.
  • Headless patterns separate behavior from presentation, ideal for design systems.
  • Reusable libraries demand solid APIs, accessibility, theming, and documentation.

Recap 🔄

Advanced component APIs let you craft building blocks that fit varied use cases: forward refs for DOM access, imperative handles for narrow control, compound/headless patterns for composition, and robust library practices to share components across teams without coupling design to behavior.

Last updated on