Skip to main content

react-rerender-optimization

Reduces unnecessary React component re-renders using targeted memoization (React.memo, useMemo, useCallback), stable key props for list rendering, atomic state management, and context value stabilization.

Ir a la instalación

Datos de origen

Repositorio
paulpas/agent-skill-router
Última actividad en el origen
9 de junio de 2026 a las 16:28
Idioma detectado de SKILL.md
inglés
Estrellas
4
Forks
1

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
react-rerender-optimization
description
Reduces unnecessary React component re-renders using targeted memoization (React.memo, useMemo, useCallback), stable key props for list rendering, atomic state management, and context value stabilization.
license
MIT
compatibility
opencode
archetypes
["tactical","generation"]
anti_triggers
["brainstorming","vague ideation","long-form architecture"]
response_profile
{"verbosity":"low","directive_strength":"high","abstraction_level":"operational"}
metadata
{"version":"1.0.0","domain":"coding","triggers":"react rerender, memoization, react memo, useMemo, useCallback, render optimization, avoid rerender","role":"implementation","scope":"implementation","output-format":"code","content-types":["code","guidance","do-dont","examples"],"related-skills":"react-client-data-fetching, react-async-waterfalls, react-composition-patterns","author":"https://github.com/vercel","source":"https://github.com/vercel-labs/agent-skills"}
# React Re-Render Optimization Minimizes unnecessary re-renders in React component trees by applying targeted memoization (`React.memo`, `useMemo`, `useCallback`), using stable and unique key props for list rendering, colocating state to its smallest necessary scope, stabilizing context provider values, and splitting large state objects into atomic units. Every optimization is driven by profiler measurements — never by intuition alone. ## TL;DR Checklist - [ ] Profile with React DevTools Profiler before applying any memoization - [ ] Wrap frequently re-rendering child components with `React.memo()` - [ ] Memoize expensive computations with `useMemo()` — not every inline value - [ ] Pass stable function references with `useCallback()` to memoized children - [ ] Use stable, unique IDs (not array index) as list keys - [ ] Split state into atomic `useState` calls instead of one large object - [ ] Use `useRef` for values that should NOT trigger re-renders - [ ] Stabilize context provider values with `useMemo()` to prevent cascading re-renders - [ ] Move context provider out of the same component that renders children --- ## When to Use Use this skill when: - React DevTools Profiler shows excessive re-renders of deep component trees - UI feels laggy or janky during state updates (not data loading) - Components re-render when their props haven't changed (wasted renders) - Large lists re-render entirely when only one item changes - Context providers cause all consumers to re-render unnecessarily - Building animation-heavy interfaces where every frame matters - Investigating performance issues with complex forms or data entry screens --- ## When NOT to Use Avoid this skill for: - Initial development — write correct code first, optimize later (premature optimization) - Applications with simple component trees where re-render cost is negligible - Components that re-render infrequently (once per user action, not per keystroke) - Parts of the tree where re-renders are cheap (leaf nodes with no children) - Over-memoizing: wrapping everything in `React.memo` and `useCallback` adds overhead --- ## Core Workflow 1. **Profile First** — Open React DevTools Profiler, record an interaction, and inspect the flamegraph. Identify components that re-render unnecessarily. **Checkpoint:** Note which components re-render without prop changes — those are optimization targets. 2. **Apply Targeted React.memo** — Wrap components that receive the same props but re-render due to parent state changes. Verify the component renders less frequently in the profiler after adding `React.memo`. **Checkpoint:** Re-profile to confirm the memo actually prevented re-renders. 3. **Memoize Expensive Computations** — Use `useMemo()` for derived data (filtered lists, sorted arrays, formatted values) that are expensive to compute. Do NOT memoize trivial calculations like string concatenation. **Checkpoint:** Verify the computation runs once when dependencies change, not on every render. 4. **Stabilize Callbacks with useCallback** — Pass `useCallback()` to children wrapped in `React.memo()` so they receive stable function references. Without this, `React.memo` can't prevent re-renders because the callback prop changes every render. 5. **Optimize Keys and State** — Replace array index keys with stable IDs. Split large `useState` objects into atomic pieces. **Checkpoint:** Verify the profiler shows fewer re-renders in list items and form fields. 6. **Stabilize Context** — Wrap context provider values in `useMemo()`. Move the provider into its own component to prevent the provider value from changing when unrelated parent state changes. --- ## Implementation Patterns ### Pattern 1: React.memo with useCallback (BAD vs. GOOD) ```tsx import { memo, useState, useCallback } from 'react'; // ── Child component ───────────────────────────────────── interface ExpensiveItemProps { title: string; onSelect: (id: string) => void; selected: boolean; } const ExpensiveItem = memo(function ExpensiveItem({ title, onSelect, selected, }: ExpensiveItemProps) { // Simulates expensive rendering (large list item with charts, etc.) console.log(`Rendering: ${title}`); return ( <div className={`item ${selected ? 'item--selected' : ''}`} onClick={() => onSelect(title)} > {title} </div> ); }); // ── Parent component ──────────────────────────────────── function ItemList() { const [selected, setSelected] = useState<string | null>(null); const [search, setSearch] = useState(''); const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']; // ❌ BAD: New function created every render — memoized child can't skip re-render const handleBadSelect = (id: string) => { setSelected(id); }; // ✅ GOOD: Stable callback identity via useCallback const handleSelect = useCallback((id: string) => { setSelected(id); }, []); // No deps — setSelected is stable return ( <div> <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search..." /> {items.map(item => ( <ExpensiveItem key={item} title={item} onSelect={handleSelect} // Stable reference — memo works! selected={selected === item} /> ))} </div> ); } // With BAD handleBadSelect: typing in search causes ALL items to re-render // With GOOD handleSelect: only the changed item re-renders when selected changes ``` ### Pattern 2: useMemo for Expensive Computations ```tsx import { useMemo } from 'react'; interface Transaction { id: string; amount: number; category: string; date: string; } function TransactionSummary({ transactions, categoryFilter }: { transactions: Transaction[]; categoryFilter: string; }) { // ❌ BAD: Filtered + sorted arrays recomputed on EVERY render const filtered = transactions .filter(t => categoryFilter === 'all' || t.category === categoryFilter) .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); // ✅ GOOD: Only recompute when transactions or categoryFilter actually change const filteredTransactions = useMemo( () => transactions .filter(t => categoryFilter === 'all' || t.category === categoryFilter) .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()), [transactions, categoryFilter] ); const totals = useMemo( () => filteredTransactions.reduce( (acc, t) => ({ ...acc, [t.category]: (acc[t.category] || 0) + t.amount }), {} as Record<string, number> ), [filteredTransactions] ); return ( <div> {filteredTransactions.map(t => ( <div key={t.id}>{t.category}: ${t.amount}</div> ))} <div>Category breakdown: {JSON.stringify(totals)}</div> </div> ); } ``` ### Pattern 3: Stable Keys for Lists (BAD vs. GOOD) ```tsx // ❌ BAD: Array index as key — causes re-renders, state bugs, and lost focus function BadTodoList({ todos }: { todos: Todo[] }) { return ( <ul> {todos.map((todo, index) => ( <TodoItem key={index} todo={todo} /> // Problem 1: Adding an item shifts all keys — all items re-render // Problem 2: Removing an item shifts keys — wrong items get new state // Problem 3: Reordering breaks completely — React can't track identity ))} </ul> ); } // ✅ GOOD: Stable unique ID as key function GoodTodoList({ todos }: { todos: Todo[] }) { return ( <ul> {todos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> // IDs stay stable across add/remove/reorder — only changed items re-render ))} </ul> ); } // ⚠️ ACCEPTABLE (last resort): Stable combined key when no ID exists function AcceptableFallback({ items }: { items: { name: string; type: string }[] }) { return ( <ul> {items.map(item => ( <li key={`${item.type}-${item.name}`}> {item.name} </li> ))} </ul> ); } ``` ### Pattern 4: Atomic State Instead of Large Objects ```tsx import { useState } from 'react'; interface FormData { name: string; email: string; address: string; city: string; country: string; newsletter: boolean; theme: 'light' | 'dark'; fontSize: number; } // ❌ BAD: Single state object — changing ANY field re-renders ALL form components function BadForm() { const [form, setForm] = useState<FormData>({ /* initial values */ }); const updateField = (field: keyof FormData, value: string | boolean | number) => { setForm(prev => ({ ...prev, [field]: value })); // Every field change triggers one re-render of the entire form }; return ( <div> <input value={form.name} onChange={e => updateField('name', e.target.value)} /> <input value={form.email} onChange={e => updateField('email', e.target.value)} /> {/* Full re-render on every keystroke — even for unrelated fields */} </div> ); } // ✅ GOOD: Atomic state — each field re-renders independently function GoodForm() { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [address, setAddress] = useState(''); const [newsletter, setNewsletter] = useState(false); const [theme, setTheme] = useState<'light' | 'dark'>('light'); // Typing in name only re-renders the name input (if memoized properly or in separate component) return ( <div> <NameInput value={name} onChange={setName} /> <EmailInput value={email} onChange={setEmail} /> <AddressInput value={address} onChange={setAddress} /> <NewsletterToggle value={newsletter} onChange={setNewsletter} /> </div> ); } ``` ### Pattern 5: Context Value Stabilization ```tsx import { createContext, useContext, useMemo, useState } from 'react'; interface AuthContextValue { user: { id: string; name: string } | null; login: (token: string) => void; logout: () => void; } // ❌ BAD: Context provider in a component that also renders children // Any parent state change creates a new context value → ALL consumers re-render function BadAuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState<{ id: string; name: string } | null>(null); // This is a NEW object every render — every consumer re-renders const value = { user, login: (token: string) => setUser({ id: '1', name: 'User' }), logout: () => setUser(null), }; return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; } // ✅ GOOD: Context value memoized so only relevant changes trigger re-renders function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState<{ id: string; name: string } | null>(null); // Stable callbacks — identity doesn't change across renders const login = useCallback((token: string) => { setUser({ id: '1', name: 'User' }); }, []); const logout = useCallback(() => { setUser(null); }, []); // Memoized context value — only changes when user changes const value = useMemo<AuthContextValue>( () => ({ user, login, logout }), [user, login, logout] ); return ( <AuthContext.Provider value={value}> {children} </AuthContext.Provider> ); } ``` ### Pattern 6: useRef for Non-Rendering Values ```tsx import { useRef, useState, useEffect } from 'react'; function Timer() { const [elapsed, setElapsed] = useState(0); // ❌ BAD: State for values that don't affect rendering const [intervalId, setIntervalId] = useState<number | null>(null); // Changing intervalId triggers a re-render even though nothing visible changes // ✅ GOOD: useRef for persistent values that don't trigger re-renders const intervalRef = useRef<number | null>(null); const startTimeRef = useRef<number>(Date.now()); useEffect(() => { intervalRef.current = window.setInterval(() => { setElapsed(Date.now() - startTimeRef.current); }, 100); return () => { if (intervalRef.current !== null) { clearInterval(intervalRef.current); } }; }, []); return <div>Elapsed: {elapsed}ms</div>; } ``` --- ## Constraints ### MUST DO - Profile before memoizing — measure the performance problem, don't guess - Use stable, unique IDs as list keys — never use array index for dynamic lists - Stabilize context provider values with `useMemo()` to prevent cascading re-renders - Split state into the smallest logical units (separate `useState` calls) - Use `useRef` for values that persist across renders but shouldn't trigger re-renders - Wrap `useCallback` around callbacks passed to memoized child components ### MUST NOT DO - Over-memoize — only memoize when the profiler shows a measurable benefit - Use array index as key in dynamic lists (add/remove/reorder causes bugs) - Create new objects or arrays in the render body and pass them as props (breaks memoization) - Put context provider in the same component that renders children (creates new value on every parent render) - Memoize trivial computations — `useMemo` has overhead; only use it for genuinely expensive operations --- ## Related Skills | Skill | Purpose | |---|---| | `react-composition-patterns` | Proper component composition reduces prop changes that trigger re-renders | | `react-client-data-fetching` | Query caching reduces unnecessary re-renders from data refetches | | `react-async-waterfalls` | Parallel data fetching prevents cascading re-renders from sequential data loads |
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub