--- name: react-composable-components description: > Write and refactor React components to be small, composable, and customizable, doing one thing well. Improve rendering performance, maintainability, and reusability by leveraging compound components, prop spreading, and utility class merging. Apply these patterns when authoring new components or breaking down large monolithic ones. --- # React Composable Components Patterns for authoring React components that are small, focused, and highly composable. ## Core idea Large components that manage too much state and render multiple distinct UI areas suffer from poor readability, reusability, and render performance. **Avoid:** Massive monolithic components that accept dozens of props (`hasHeader`, `showFooter`). **Prefer:** Breaking UI down into atomic, composable pieces (Compound Components) that transparently pass HTML attributes and merge utility classes for easy customization. --- ## 1. Extract Inline Render Methods If a component has methods like `renderStoryScreen()`, extract them into independent child components. This prevents unnecessary re-renders of the entire monolith when small parts of the state change. ```tsx // Prefer returning separate components instead of calling inline logic blocks function App() { const [screen, setScreen] = useState('START'); return (
{screen === 'START' && setScreen('STORY')} />} {screen === 'STORY' && }
); } ``` --- ## 2. Compound Components over Configuration Props Instead of passing complex objects into a single component (`} />`), expose the sub-components. This allows the consumer to inject custom content or omit parts entirely. ```tsx export function App() { return ( Adventure Begins {/* Consumer fully controls the layout and sub-components! */} Hero ); } ``` --- ## 3. Context for Shared State When building complex compound components (like Tabs or Accordions), use a local React Context to share state internally, removing the need for consumers to manually drill props (`isOpen={isOpen} setIsOpen={setIsOpen}`). ```tsx import { createContext, useContext, useState } from "react"; const TabsContext = createContext<{ activeTab: string; setActiveTab: (v: string) => void } | null>(null); export function Tabs({ defaultValue, children }) { const [activeTab, setActiveTab] = useState(defaultValue); return {children}; } export function Tab({ value, children }) { const ctx = useContext(TabsContext); const isActive = ctx?.activeTab === value; return ; } export function TabContent({ value, children }) { const ctx = useContext(TabsContext); return ctx?.activeTab === value ?
{children}
: null; } ``` **Usage:** ```tsx Home Settings Home Content! Settings Content! ``` --- ## 4. Transparent Props & ClassName Merging Composable components should act like native HTML elements. Accept `className` and `...props` to give consumers full control without bloating the component API. ```tsx import { forwardRef } from "react"; import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs)); export const Button = forwardRef>( ({ className, variant = "primary", ...props }, ref) => (