--- name: react-component-dev description: Build React components with proper patterns, accessibility, and composition. Use when creating new components, refactoring existing ones, or reviewing component architecture. Covers forwardRef, prop design, accessibility, file organization, and testing approaches. --- # React Component Development Patterns for building composable, accessible, well-structured React components. ## Core Principles 1. **Composition over configuration** - Props enable customization, not enumerate options 2. **Forwarding refs** - Components that render DOM elements forward refs 3. **Accessibility first** - Keyboard, screen readers, reduced motion 4. **Predictable APIs** - Consistent prop patterns across components ## Component Template ```tsx import { forwardRef, type ComponentPropsWithoutRef } from "react" import { cn } from "@/lib/utils" type ButtonProps = ComponentPropsWithoutRef<"button"> & { variant?: "default" | "outline" | "ghost" size?: "sm" | "md" | "lg" } const Button = forwardRef( ({ className, variant = "default", size = "md", ...props }, ref) => { return ( // Loading states // Expandable content ``` ### Reduced Motion ```tsx const prefersReducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)") // Or in CSS @media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; } } ``` ## State Management ### Local State Use `useState` for: - UI state (open/closed, selected) - Form inputs (controlled) - Ephemeral data (hover, focus) ### Derived State ```tsx // Bad: useEffect to sync const [fullName, setFullName] = useState("") useEffect(() => { setFullName(`${firstName} ${lastName}`) }, [firstName, lastName]) // Good: useMemo const fullName = useMemo( () => `${firstName} ${lastName}`, [firstName, lastName] ) // Best: Just compute it (if cheap) const fullName = `${firstName} ${lastName}` ``` ### Complex State ```tsx // useReducer for multi-field updates const [state, dispatch] = useReducer(reducer, initialState) // Or extract to custom hook const dialog = useDialogState() ``` ## Event Handlers ### Prop Naming ```tsx // Internal handler const handleClick = () => { ... } // Prop callbacks: on[Event] type Props = { onClick?: () => void onOpenChange?: (open: boolean) => void onValueChange?: (value: string) => void } ``` ### Composing Handlers ```tsx const Button = forwardRef( ({ onClick, ...props }, ref) => { const handleClick = (e: React.MouseEvent) => { // Internal logic trackClick() // Call user's handler onClick?.(e) } return ) await userEvent.click(screen.getByRole("button")) expect(handleClick).toHaveBeenCalledOnce() }) it("is disabled when disabled prop is true", () => { render() expect(screen.getByRole("button")).toBeDisabled() }) }) ``` ## Anti-Patterns ### Prop Drilling ```tsx // Bad: Passing props through many layers // Better: Context for deep trees {/* useContext inside */} ``` ### Premature Abstraction ```tsx // Bad: Generic component nobody asked for // Good: Specific component for the use case ``` ### Boolean Prop Explosion ```tsx // Bad