Skip to main content

react-performance-patterns

Battle-tested patterns for optimizing React applications, from component design to bundle optimization

Jump to install

Source facts

Repository
anaghkanungo7/agent-skills
Last source activity
February 12, 2026 at 15:26
Detected SKILL.md language
English
Stars
1
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
3 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
react-performance-patterns
description
Battle-tested patterns for optimizing React applications, from component design to bundle optimization
# React Performance Patterns You are an expert in React performance optimization. You help developers identify performance bottlenecks, implement efficient rendering patterns, and build fast, responsive React applications. Your guidance is based on real-world production experience and current best practices. ## Core Performance Principles ### 1. Measure Before Optimizing Never optimize blindly. Always profile first: ```tsx // Use React DevTools Profiler import { Profiler } from 'react'; function onRenderCallback( id: string, phase: 'mount' | 'update', actualDuration: number, baseDuration: number, startTime: number, commitTime: number ) { console.log({ id, phase, actualDuration }); } <Profiler id="ExpensiveComponent" onRender={onRenderCallback}> <ExpensiveComponent /> </Profiler> ``` **Key metrics to track:** - Render count - Render duration - Component mount/update time - Bundle size - Network waterfall - Core Web Vitals (LCP, INP, CLS) ### 2. Avoid Unnecessary Renders React re-renders when: - State changes - Props change - Parent re-renders - Context value changes Your job: minimize wasted renders. ### 3. Code Split Aggressively Users shouldn't download code for pages they never visit. ### 4. Optimize Heavy Operations Move expensive calculations off the main thread or cache results. ## Pattern 1: Memoization ### React.memo() - Prevent Component Re-renders ```tsx // Before: Re-renders on every parent render function UserCard({ user }: { user: User }) { return <div>{user.name}</div>; } // After: Only re-renders when user prop changes const UserCard = memo(({ user }: { user: User }) => { return <div>{user.name}</div>; }); ``` **When to use:** - Pure functional components - Components that render frequently with same props - Expensive render operations - List items **When NOT to use:** - Component always receives new props - Props contain objects/arrays created inline - Component is already fast ### useMemo() - Memoize Expensive Calculations ```tsx function DataTable({ data, filters }: Props) { // Bad: Recalculates on every render const filtered = data.filter(item => filters.every(f => f.fn(item)) ); // Good: Only recalculates when data or filters change const filtered = useMemo( () => data.filter(item => filters.every(f => f.fn(item))), [data, filters] ); return <Table data={filtered} />; } ``` **When to use:** - Filtering/sorting large arrays - Complex calculations - Derived data that's expensive to compute - Creating objects/arrays passed as props **Cost/benefit check:** ```tsx // Not worth it (simple operation) const doubled = useMemo(() => count * 2, [count]); // Worth it (expensive operation) const sorted = useMemo( () => items.sort((a, b) => expensiveCompare(a, b)), [items] ); ``` ### useCallback() - Memoize Functions ```tsx function Parent() { // Bad: New function on every render const handleClick = () => { doSomething(); }; // Good: Stable function reference const handleClick = useCallback(() => { doSomething(); }, []); return <MemoizedChild onClick={handleClick} />; } ``` **When to use:** - Passing callbacks to memoized children - Dependencies in useEffect/useMemo - Creating stable event handlers - Working with debounce/throttle **Common mistake:** ```tsx // Mistake: useCallback with new object in dependency const handleClick = useCallback(() => { doSomething(data); }, [data]); // If 'data' is a new object each render, callback still changes // Better: Destructure stable values const { id, name } = data; const handleClick = useCallback(() => { doSomething({ id, name }); }, [id, name]); ``` ## Pattern 2: Code Splitting & Lazy Loading ### Component-Level Code Splitting ```tsx import { lazy, Suspense } from 'react'; // Bad: Bundles everything upfront import HeavyChart from './HeavyChart'; import AdminPanel from './AdminPanel'; // Good: Load on demand const HeavyChart = lazy(() => import('./HeavyChart')); const AdminPanel = lazy(() => import('./AdminPanel')); function Dashboard() { const [showChart, setShowChart] = useState(false); return ( <div> <button onClick={() => setShowChart(true)}> Show Chart </button> {showChart && ( <Suspense fallback={<Skeleton />}> <HeavyChart /> </Suspense> )} </div> ); } ``` ### Route-Based Code Splitting ```tsx // Next.js: Automatic code splitting by route // app/dashboard/page.tsx export default function DashboardPage() { return <Dashboard />; } // React Router: Manual code splitting const Dashboard = lazy(() => import('./pages/Dashboard')); const Profile = lazy(() => import('./pages/Profile')); function App() { return ( <Routes> <Route path="/dashboard" element={ <Suspense fallback={<Spinner />}> <Dashboard /> </Suspense> } /> <Route path="/profile" element={ <Suspense fallback={<Spinner />}> <Profile /> </Suspense> } /> </Routes> ); } ``` ### Preloading for Better UX ```tsx // Preload on hover (faster perceived performance) function Navigation() { const handleMouseEnter = () => { // Preload Dashboard chunk import('./pages/Dashboard'); }; return ( <Link to="/dashboard" onMouseEnter={handleMouseEnter} > Dashboard </Link> ); } ``` ## Pattern 3: Virtualization (Long Lists) For lists with 100+ items, render only what's visible. ### Using react-window ```tsx import { FixedSizeList } from 'react-window'; // Bad: Rendering 10,000 items function BadList({ items }: { items: Item[] }) { return ( <div> {items.map(item => ( <ItemRow key={item.id} item={item} /> ))} </div> ); } // Good: Virtualized (only renders ~20 visible items) function GoodList({ items }: { items: Item[] }) { return ( <FixedSizeList height={600} itemCount={items.length} itemSize={50} width="100%" > {({ index, style }) => ( <div style={style}> <ItemRow item={items[index]} /> </div> )} </FixedSizeList> ); } ``` **Performance impact:** - 10,000 items without virtualization: 5-10 second render - 10,000 items with virtualization: 50-100ms render ### Dynamic Size Lists ```tsx import { VariableSizeList } from 'react-window'; function DynamicList({ items }: { items: Item[] }) { const getItemSize = (index: number) => { // Return height based on content return items[index].type === 'large' ? 120 : 60; }; return ( <VariableSizeList height={600} itemCount={items.length} itemSize={getItemSize} width="100%" > {({ index, style }) => ( <div style={style}> <ItemRow item={items[index]} /> </div> )} </VariableSizeList> ); } ``` ## Pattern 4: Optimize Context Usage ### Problem: Context Causes Unnecessary Re-renders ```tsx // Bad: Every consumer re-renders on any state change const AppContext = createContext<AppState>(null); function AppProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState<User | null>(null); const [theme, setTheme] = useState<'light' | 'dark'>('light'); const [notifications, setNotifications] = useState<Notification[]>([]); const value = { user, setUser, theme, setTheme, notifications, setNotifications }; return <AppContext.Provider value={value}>{children}</AppContext.Provider>; } // This re-renders when ANYTHING in context changes function UserAvatar() { const { user } = useContext(AppContext); // Re-renders on theme change! return <Avatar user={user} />; } ``` ### Solution 1: Split Contexts ```tsx // Good: Separate concerns const UserContext = createContext<UserState>(null); const ThemeContext = createContext<ThemeState>(null); const NotificationContext = createContext<NotificationState>(null); // Now components only subscribe to what they need function UserAvatar() { const { user } = useContext(UserContext); // Only re-renders on user change return <Avatar user={user} />; } ``` ### Solution 2: Context Selectors ```tsx // Using zustand (or similar library) import create from 'zustand'; const useStore = create<AppState>((set) => ({ user: null, theme: 'light', setUser: (user) => set({ user }), setTheme: (theme) => set({ theme }), })); // Select only what you need function UserAvatar() { const user = useStore((state) => state.user); // Only re-renders on user change return <Avatar user={user} />; } function ThemeSwitcher() { const theme = useStore((state) => state.theme); // Only re-renders on theme change return <ThemeToggle theme={theme} />; } ``` ### Solution 3: Memoize Context Value ```tsx function AppProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState<User | null>(null); // Prevent new object on every render const value = useMemo( () => ({ user, setUser }), [user] ); return <AppContext.Provider value={value}>{children}</AppContext.Provider>; } ``` ## Pattern 5: Debounce & Throttle ### Debounce: Wait for User to Stop Typing ```tsx import { useDebouncedCallback } from 'use-debounce'; function SearchInput() { const [query, setQuery] = useState(''); // Bad: API call on every keystroke const handleChange = (e: ChangeEvent<HTMLInputElement>) => { const value = e.target.value; setQuery(value); searchAPI(value); // 🔥 Too many requests! }; // Good: Wait 300ms after user stops typing const debouncedSearch = useDebouncedCallback( (value: string) => { searchAPI(value); }, 300 ); const handleChange = (e: ChangeEvent<HTMLInputElement>) => { const value = e.target.value; setQuery(value); debouncedSearch(value); }; return <input value={query} onChange={handleChange} />; } ``` **Custom debounce hook:** ```tsx function useDebounce<T>(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(handler); }; }, [value, delay]); return debouncedValue; } // Usage function SearchResults() { const [query, setQuery] = useState(''); const debouncedQuery = useDebounce(query, 300); useEffect(() => {
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub