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:
- API design: stable prop names, clear defaults.
- Accessibility: ARIA roles, keyboard interactions, focus traps.
- Theming: expose className/style overrides or render props.
- Documentation: MDX stories, prop tables, examples.
- Testing: unit + visual regression to prevent breaking changes.
🧠 Think consumer-first: components should be composable, themeable, and predictable.
Key Takeaways ✅
forwardRefanduseImperativeHandleexpose 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