--- name: react-patterns description: Modern React patterns, hooks, composition, and performance optimization. Use for React component development. --- # React Patterns Skill ## Core Principles - **Composition Over Inheritance**: Build with small, reusable components - **Unidirectional Data Flow**: Props down, events up - **Declarative UI**: Describe what, not how - **Immutable State**: Never mutate state directly - **Single Responsibility**: One component, one purpose ## Component Patterns ### Functional Components (Default) ```typescript // Good: Typed props interface interface ButtonProps { label: string; onClick: () => void; variant?: 'primary' | 'secondary'; disabled?: boolean; } export function Button({ label, onClick, variant = 'primary', disabled = false }: ButtonProps) { return ( ); } ``` ### Composition Patterns ```typescript // Good: Children prop for composition interface CardProps { children: React.ReactNode; title?: string; } export function Card({ children, title }: CardProps) { return (
{title &&

{title}

} {children}
); } // Good: Compound components export function Tabs({ children }: { children: React.ReactNode }) { return
{children}
; } Tabs.Panel = function TabPanel({ children }: { children: React.ReactNode }) { return
{children}
; }; // Usage Content 1 Content 2 ``` ## Hooks Best Practices ### useState ```typescript // Good: Functional updates for derived state const [count, setCount] = useState(0); setCount(prev => prev + 1); // Good: Lazy initialization for expensive computation const [data, setData] = useState(() => expensiveComputation()); // Good: Multiple state variables for unrelated state const [name, setName] = useState(''); const [email, setEmail] = useState(''); // Bad: Single object for unrelated state (causes unnecessary re-renders) const [form, setForm] = useState({ name: '', email: '' }); ``` ### useEffect ```typescript // Good: Cleanup function useEffect(() => { const subscription = api.subscribe(data => setData(data)); return () => subscription.unsubscribe(); }, []); // Good: Dependency array with all dependencies useEffect(() => { fetchUser(userId).then(setUser); }, [userId]); // Good: Separate effects for separate concerns useEffect(() => { // Effect 1: Fetch user fetchUser(userId).then(setUser); }, [userId]); useEffect(() => { // Effect 2: Track analytics analytics.track('page_view', { userId }); }, [userId]); ``` ### Custom Hooks ```typescript // Good: Extract reusable logic function useLocalStorage(key: string, initialValue: T) { const [value, setValue] = useState(() => { const stored = localStorage.getItem(key); return stored ? JSON.parse(stored) : initialValue; }); useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]); return [value, setValue] as const; } // Good: Async data fetching hook function useAsync(asyncFn: () => Promise, deps: React.DependencyList) { const [state, setState] = useState<{ loading: boolean; data?: T; error?: Error; }>({ loading: true }); useEffect(() => { let cancelled = false; setState({ loading: true }); asyncFn() .then(data => !cancelled && setState({ loading: false, data })) .catch(error => !cancelled && setState({ loading: false, error })); return () => { cancelled = true; }; }, deps); return state; } ``` ### useMemo and useCallback ```typescript // Good: Memoize expensive computations const sortedItems = useMemo(() => { return items.sort((a, b) => a.name.localeCompare(b.name)); }, [items]); // Good: Memoize callbacks passed to child components const handleClick = useCallback(() => { doSomething(id); }, [id]); // Bad: Premature optimization const value = useMemo(() => x + y, [x, y]); // Unnecessary for simple operations ``` ### useRef ```typescript // Good: DOM references const inputRef = useRef(null); useEffect(() => { inputRef.current?.focus(); }, []); // Good: Mutable values that don't trigger re-renders const countRef = useRef(0); function handleClick() { countRef.current += 1; console.log('Clicked', countRef.current, 'times'); } ``` ## State Management Patterns ### Lifting State Up ```typescript // Good: Shared state in common ancestor function Parent() { const [selected, setSelected] = useState(null); return ( <> ); } ``` ### Context for Deep Props ```typescript // Good: Context for theme, auth, etc. interface ThemeContextValue { theme: 'light' | 'dark'; toggleTheme: () => void; } const ThemeContext = createContext(undefined); export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState<'light' | 'dark'>('light'); const toggleTheme = useCallback(() => { setTheme(prev => prev === 'light' ? 'dark' : 'light'); }, []); return ( {children} ); } export function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within ThemeProvider'); } return context; } ``` ### Reducer for Complex State ```typescript // Good: useReducer for complex state logic type State = { items: Item[]; filter: string; sort: 'asc' | 'desc'; }; type Action = | { type: 'ADD_ITEM'; item: Item } | { type: 'REMOVE_ITEM'; id: string } | { type: 'SET_FILTER'; filter: string } | { type: 'TOGGLE_SORT' }; function reducer(state: State, action: Action): State { switch (action.type) { case 'ADD_ITEM': return { ...state, items: [...state.items, action.item] }; case 'REMOVE_ITEM': return { ...state, items: state.items.filter(i => i.id !== action.id) }; case 'SET_FILTER': return { ...state, filter: action.filter }; case 'TOGGLE_SORT': return { ...state, sort: state.sort === 'asc' ? 'desc' : 'asc' }; } } function Component() { const [state, dispatch] = useReducer(reducer, initialState); // ... } ``` ## Performance Optimization ### React.memo ```typescript // Good: Memoize expensive components export const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }: { data: Data }) { // Expensive rendering logic return
{/* ... */}
; }); // Good: Custom comparison export const UserCard = React.memo( function UserCard({ user }: { user: User }) { return
{user.name}
; }, (prev, next) => prev.user.id === next.user.id ); ``` ### Code Splitting ```typescript // Good: Lazy load routes const Dashboard = lazy(() => import('./Dashboard')); const Settings = lazy(() => import('./Settings')); function App() { return ( }> } /> } /> ); } ``` ### Virtualization ```typescript // Good: Virtualize long lists import { FixedSizeList } from 'react-window'; function VirtualList({ items }: { items: Item[] }) { return ( {({ index, style }) => (
{items[index].name}
)}
); } ``` ## Error Boundaries ```typescript // Good: Error boundary for graceful failures class ErrorBoundary extends React.Component< { children: React.ReactNode; fallback: React.ReactNode }, { hasError: boolean } > { state = { hasError: false }; static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error: Error, info: React.ErrorInfo) { console.error('Error caught:', error, info); } render() { if (this.state.hasError) { return this.props.fallback; } return this.props.children; } } ``` ## Form Handling ```typescript // Good: Controlled components function LoginForm() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); login({ email, password }); }; return (
setEmail(e.target.value)} /> setPassword(e.target.value)} />
); } ``` ## Testing Patterns ```typescript // Good: Test user behavior, not implementation import { render, screen, fireEvent } from '@testing-library/react'; test('increments counter on button click', () => { render(); const button = screen.getByRole('button', { name: /increment/i }); const count = screen.getByText(/count: 0/i); fireEvent.click(button); expect(screen.getByText(/count: 1/i)).toBeInTheDocument(); }); ``` ## Common Pitfalls ### Avoid Inline Object/Array Creation in Props ```typescript // Bad: Creates new object every render // Good: Define outside or use useMemo const style = { margin: 10 }; ``` ### Avoid Conditional Hooks ```typescript // Bad: Hooks must be called unconditionally if (condition) { useEffect(() => { /* ... */ }, []); } // Good: Condition inside hook useEffect(() => { if (condition) { // ... } }, [condition]); ``` ### Key Prop for Lists ```typescript // Bad: Index as key (causes issues with reordering) {items.map((item, index) => )} // Good: Stable unique identifier {items.map(item => )} ```