name: building-react-components description: Designs and writes React components with clean props APIs, composition over configuration, correct state placement, and hooks-based logic reuse. Use when the user asks to create, refactor, or review a React component, design a component API, decide where state should live, extract a custom hook, or fix prop drilling or unnecessary re-renders. Do not use for CSS layout mechanics (designing-responsive-layouts) or non-React frameworks — adapt the principles manually there.
Building React Components
When to use / when NOT to use
- Use for: creating or refactoring React components, designing props APIs, placing state, extracting custom hooks, reviewing component structure.
- Do NOT use for: CSS layout/breakpoint work (designing-responsive-layouts), styling systems (theming-design-tokens), Vue/Svelte/Angular components, or backend data modeling.
Core rules
-
Narrow, typed props — no boolean explosions. One
variantunion beats three flags that can contradict each other.- ✅
variant: 'primary' | 'danger' | 'ghost' - ❌
isPrimary,isDanger,isGhostas three separate booleans
- ✅
-
Composition over configuration. When a component grows a prop per content slot, switch to
childrenor slot components.- ✅
<Card><Card.Header>…</Card.Header><Card.Body>…</Card.Body></Card> - ❌
<Card headerText="…" headerIcon="…" bodyContent={…} footerButtons={…} />
- ✅
-
State lives at the lowest component that needs it; lift only when shared. Before
useState, pick the state's home: server data → query library cache; shareable/bookmarkable → URL; everything else → local state closest to use.- ✅ search text in the
SearchBox, results in the query cache, filters in the URL - ❌ every field of a page hoisted into one context "to be safe"
- ✅ search text in the
-
Pick controlled or uncontrolled per input and stay consistent. Controlled (
value+onChange) when other UI reacts per keystroke; uncontrolled (defaultValue+ read on submit) for plain forms.- ❌
valuewithoutonChange, or switching between the two mid-lifecycle
- ❌
-
Extract reusable logic into custom hooks, not wrapper components. A hook named
useXreturning plain values beats render-props or HOC indirection.- ✅
const { data, error } = usePolling(url, 5000) - ❌
<PollingProvider render={(data) => …} />
- ✅
-
Memoize only after measuring. No
React.memo/useMemo/useCallbackby default; add them when the Profiler shows a real re-render cost, and comment why.- ❌ wrapping every callback in
useCallback"for performance"
- ❌ wrapping every callback in
-
One component per file, named exports, file named after the component.
UserMenu.tsxexportsUserMenu; its private subcomponents stay in the same file until reused elsewhere. -
Derive, don't sync. Values computable from existing props/state are computed during render — never mirrored into state with an effect.
- ✅
const fullName = first + ' ' + last - ❌
useEffect(() => setFullName(first + ' ' + last), [first, last])
- ✅
Workflow
- Name the component and write its props type first — if the type needs more than ~7 props or any boolean pair, redesign with rules 1–2.
- Decide each piece of state's home (rule 3) before writing any
useState. - Implement render logic; derive values instead of syncing state (rule 8).
- Extract any logic used twice into a custom hook (rule 5).
- Self-review: re-check every prop against rules 1–2, every
useStateagainst rules 3 and 8, every memoization against rule 6. Fix violations before delivering.
Edge cases & failure modes
- Existing codebase conventions conflict with these rules → match the codebase; note the divergence in one sentence, don't refactor uninvited.
- Class components in the file being edited → keep the class style for small edits; propose (don't perform) a hooks migration.
- Prop drilling more than 2 levels → prefer composition (pass the composed element down) before reaching for context.
- Server components (Next.js/RSC) → hooks and state are client-only; add
'use client'only at the interactive leaf, not the page root.
References
Copy-paste patterns and gotchas: see references/patterns.md