
Accelint React Best Practices
- 298 installs
- 21 repo stars
- Updated August 4, 2026
- gohypergiant/agent-skills
accelint-react-best-practices is a React performance and correctness skill that applies version 1.8.0 rule references and anti-pattern gates for developers who write, refactor, review, or debug React and Next.js componen
About
accelint-react-best-practices is a Hypergiant Accelint agent skill at version 1.8.0 that encodes React performance optimization and correctness patterns for AI-assisted frontend work. The skill uses progressive disclosure: an AGENTS.md overview plus about 30 focused reference files covering re-render control, hydration mismatch prevention, effect dependency narrowing, React Compiler awareness, and React 19+ APIs such as useEffectEvent, Activity, and ref-as-prop. Seven explicit NEVER rules block nested inline components, object effect dependencies, derived-state sync loops, and forwardRef usage in React 19+. Developers reach for accelint-react-best-practices when debugging input focus loss, infinite re-renders, stale closures, SSR hydration warnings, or when reviewing React code for production-grade patterns. Optional automation scripts under scripts/ help detect anti-patterns, and a standardized audit report template is provided for multi-file reviews.
- accelint-react-best-practices
- Development
Accelint React Best Practices by the numbers
- 298 all-time installs (skills.sh)
- Ranked #1,332 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gohypergiant/agent-skills --skill accelint-react-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 298 |
|---|---|
| repo stars | ★ 21 |
| Last updated | August 4, 2026 |
| Repository | gohypergiant/agent-skills ↗ |
How do you fix React re-render and hydration bugs?
For development and infrastructure management.
Who is it for?
Frontend developers shipping React 19 or Next.js apps who need systematic guidance on re-renders, hydration, hooks, and memoization decisions.
Skip if: Teams on non-React frameworks or developers seeking only generic JavaScript style advice without component-level performance focus.
When should I use this skill?
The user edits React components, reports re-render loops, hydration mismatches, stale closures, or asks for a React performance audit.
What you get
Optimized React components, applied hook patterns, hydration-safe SSR markup, and optional structured performance audit reports.
- optimized component code
- performance audit report
- applied hook and SSR patterns
By the numbers
- Version 1.8.0 in skill metadata
- About 30 progressive reference rule files
- 7 explicit NEVER anti-patterns documented
Files
React Best Practices
Comprehensive performance optimization and best practices for React applications, designed for AI agents and LLMs working with React code.
NEVER Do React
These are the most critical anti-patterns that cause real production issues. Experts learned these the hard way through debugging sessions and performance investigations.
NEVER define components inside components — creates new component type on every render, causing full remount with state loss and DOM recreation. Results in input fields losing focus on keystroke, animations restarting unexpectedly, and useEffect cleanup/setup running on every parent render.
NEVER subscribe to searchParams/localStorage if you only read them in callbacks — causes component to re-render on every URL change or storage event even when the component doesn't display those values. Read directly in the callback instead: new URLSearchParams(window.location.search).
NEVER use object/array dependencies in useEffect — triggers effect on every render since objects are recreated with new references each time. Extract primitive values (id, name) from objects and use those as dependencies instead.
NEVER sync derived state with useState + useEffect — leads to extra re-renders, infinite loops, and stale intermediate states. Calculate derived values during render instead: const fullName = firstName + ' ' + lastName.
NEVER use client-only state (localStorage, cookies, device detection) directly in SSR components — causes hydration mismatches where server HTML doesn't match client render, resulting in React warnings, visual flickering, and broken interactivity. Use synchronous inline <script> before React hydrates.
NEVER use forwardRef in React 19+ — deprecated API. Use ref as a regular prop instead: function MyInput({ ref }) { return <input ref={ref} /> }.
NEVER create callbacks/objects/arrays inline as props to memoized components — breaks memoization since new reference is created each render. Extract to module scope, useMemo, or useCallback: const config = useMemo(() => ({ theme }), [theme]).
NEVER put user interaction logic in useEffect — if it's triggered by a button click or form submit, put it directly in the event handler. Effects are for synchronization with external systems, not user-triggered actions.
Before Optimizing Performance, Ask
Before suggesting memo/useMemo/useCallback optimizations, determine if they're needed:
1. Does this project use React Compiler?
- Search for
babel-plugin-react-compilerorreact-compiler-webpack-pluginin package.json/config files - If yes → Skip manual memoization (memo, useMemo, useCallback, hoisting static JSX) — the compiler handles these automatically
- If no → Apply all relevant optimizations from this skill
- See react-compiler-guide.md for what the compiler handles
2. Is this actually a performance problem?
- Has the user measured/profiled and identified a bottleneck?
- Or are they asking for a general review/optimization?
3. What's the scale?
- For lists: How many items? (affects whether to suggest content-visibility vs virtualization)
- For re-renders: How often does this component re-render?
How to Use
This skill uses a progressive disclosure structure to minimize context usage:
1. Start with the Overview (AGENTS.md)
Read AGENTS.md for a concise overview of all rules with one-line summaries.
2. Load Specific Rules as Needed
When you identify a relevant optimization, load the corresponding reference file for detailed implementation guidance:
Re-render Optimizations:
- defer-state-reads.md
- extract-memoized-components.md
- narrow-effect-dependencies.md
- subscribe-derived-state.md
- functional-setstate-updates.md
- lazy-state-initialization.md
- transitions-non-urgent-updates.md
- calculate-derived-state.md
- avoid-usememo-simple-expressions.md
- extract-default-parameter-value.md
- interaction-logic-in-event-handlers.md
- no-inline-components.md
- useref-for-transient-values.md
- split-combined-hooks.md
- use-deferred-value.md
Rendering Performance:
- animate-svg-wrapper.md
- css-content-visibility.md
- hoist-static-jsx.md
- optimize-svg-precision.md
- prevent-hydration-mismatch.md
- activity-component-show-hide.md
- hoist-regexp-creation.md
- use-usetransition-over-manual-loading.md
Advanced Patterns:
- store-event-handlers-refs.md
- uselatest-stable-callbacks.md
- cache-repeated-function-calls.md
- initialize-app-once.md
- effect-event-deps.md
Misc:
- named-imports.md
- no-forwardref.md
Quick References:
- quick-checklists.md
- compound-patterns.md
- react-compiler-guide.md
Automation Scripts:
- scripts/ - Helper scripts to detect anti-patterns
3. Apply the Pattern
Each reference file contains:
- ❌ Incorrect examples showing the anti-pattern
- ✅ Correct examples showing the optimal implementation
- Explanations of why the pattern matters
4. Use the Report Template
When this skill is invoked, use the standardized report format:
Template: `assets/output-report-template.md`
The report format provides:
- Executive Summary with impact assessment
- Severity levels (Critical, High, Medium, Low) for prioritization
- Impact analysis (potential bugs, type safety, maintainability, runtime failures)
- Categorization (Type Safety, Safety, State Management, Return Values, Code Quality)
- Pattern references linking to detailed guidance in references/
- Phase 2 summary table for tracking all issues
When to use the audit template:
- Skill invoked directly via
/accelint-react-best-practices <path> - User asks to "review code quality" or "audit code" across file(s), invoking skill implicitly
When NOT to use the report template:
- User asks to "fix this type error" (direct implementation)
- User asks "what's wrong with this code?" (answer the question)
- User requests specific fixes (apply fixes directly without formal report)
Examples
Example 1: Optimizing Re-renders
Task: "This component re-renders too frequently when the user scrolls"
Approach: 1. Read AGENTS.md overview 2. Identify likely cause: subscribing to continuous values (scroll position) 3. Load subscribe-derived-state.md or transitions-non-urgent-updates.md 4. Apply the pattern from the reference file
Example 2: Fixing Stale Closures
Task: "This callback always uses the old state value"
Approach: 1. Read AGENTS.md overview 2. Identify issue: stale closure in useCallback 3. Load functional-setstate-updates.md 4. Replace direct state reference with functional update
Example 3: SSR Hydration Mismatch
Task: "Getting hydration errors with localStorage theme"
Approach: 1. Read AGENTS.md overview 2. Identify issue: client-only state causing mismatch 3. Load prevent-hydration-mismatch.md 4. Implement synchronous script pattern
Using Skill Patterns Appropriately
Each reference file demonstrates ONE proven pattern, but React problems often have multiple valid solutions.
When applying patterns: 1. ✅ Present the pattern from the reference file 2. ✅ Mention alternative approaches when they exist 3. ✅ Consider user's React version, project complexity, and team preferences 4. ✅ For simple cases, suggest simpler solutions even if not in references
Example: For SSR hydration issues, prevent-hydration-mismatch.md shows the synchronous script approach, but a simple "mounted flag" pattern may be more appropriate for basic use cases.
Important Notes
React Compiler Awareness
Many manual optimization patterns (memo, useMemo, useCallback, hoisting static JSX) are automatically handled by React Compiler.
Before optimizing, check if the project uses React Compiler:
- If enabled: Skip manual memoization, but still apply state/effect/CSS optimizations
- If not enabled: Apply all relevant optimizations from this guide
See react-compiler-guide.md for a complete breakdown of what the compiler handles vs what still needs manual optimization.
React 19+ Features
This skill covers React 19 features including:
useEffectEvent(19.2+) for stable event handlers<Activity>component for preserving hidden component staterefas a prop (replaces deprecatedforwardRef)- Named imports only (no default import of React)
Performance Philosophy
- Start with correct code, then optimize
- Measure before optimizing
- Optimize slowest operations first (network > rendering > computation)
- Avoid premature optimization of trivial operations
Code Quality Principles
- Prefer simple, readable code over clever optimizations
- Only add complexity when measurements justify it
- Document non-obvious performance optimizations
Additional Resources
Catch up on React 19 features:
React Best Practices
Note:
This document is mainly for agents and LLMs to follow when maintaining, generating, or refactoring React code at Accelint. Humans may also find it useful, but guidance here is optimized for automation and consistency by AI-assisted workflows.
---
Abstract
Comprehensive performance optimization guide for React applications, designed for AI agents and LLMs. Each rule includes one-line summaries with links to detailed examples in references/. Load reference files only when implementing a specific pattern.
---
⚡ FIRST: Check React Compiler
Before suggesting memo/useMemo/useCallback optimizations:
1. Check if project uses React Compiler (search for babel-plugin-react-compiler or react-compiler-webpack-plugin in package.json/config files) 2. If React Compiler enabled: Skip manual memoization patterns — the compiler handles them automatically. Focus on:
- State management patterns (functional setState, lazy initialization)
- Effect patterns (narrow dependencies, stable callbacks)
- CSS optimizations (content-visibility)
- SSR/hydration patterns
3. If React Compiler NOT enabled: Apply all optimizations from this skill
See react-compiler-guide.md for details on what the compiler handles vs what still needs manual optimization.
---
How to Use This Guide
For agents/LLMs: 1. Scan rule summaries below to identify relevant optimizations 2. Load reference files only when implementing a specific pattern 3. Each reference is self-contained with ❌/✅ examples
Quick shortcuts:
- Re-render issues? → Section 1 (Re-render Optimizations)
- Slow rendering? → Section 2 (Rendering Performance)
- Advanced patterns? → Section 3 (Advanced Patterns)
- React 19 migration? → Section 4 (Misc)
- Not sure what's wrong? → Use Quick Diagnostic Guide below
React 19+ Resources:
---
Quick Diagnostic Guide
Use this guide to quickly identify which optimization applies based on symptoms:
Symptom → Solution:
- Component re-renders on every parent render → 1.2 Extract to Memoized Components
- Component re-renders when URL/localStorage changes but doesn't display them → 1.1 Defer State Reads
- Effect runs too frequently → 1.3 Narrow Effect Dependencies, 3.1 Store Event Handlers in Refs
- Callback has stale/old values → 1.5 Functional setState Updates, 3.2 useLatest (or useEffectEvent for React 19.2+)
- Slow initial render → 1.6 Lazy State Initialization, 2.3 Hoist Static JSX, 2.7 Hoist RegExp
- Scrolling/interaction feels janky → 2.2 CSS content-visibility, 2.1 Animate SVG Wrapper, 1.7 Transitions
- Typing/input feels sluggish → 1.7 Transitions, 1.15 useDeferredValue for Expensive Derived Renders
- Hook runs expensive computation unnecessarily → 1.14 Split Combined Hook Computations
- Window resize causes excessive re-renders → 1.4 Subscribe to Derived State
- Hydration mismatch errors (SSR/SSG) → 2.5 Prevent Hydration Mismatch
- Component state lost when hiding/showing → 2.6 Activity Component
- Infinite re-render loop → 1.5 Functional setState, 1.3 Narrow Effect Dependencies
- Large bundle size → 2.4 Optimize SVG Precision, 2.3 Hoist Static JSX
- Input fields lose focus on every keystroke → 1.13 Don't Define Components Inside Components
- Animations restart unexpectedly → 1.13 Don't Define Components Inside Components
React 19 Migration Issues:
- "forwardRef is deprecated" → 4.2 No forwardRef
- "Default import from React is deprecated" → 4.1 Named Imports
- Need stable event handlers in effects → 3.1 Store Event Handlers (useEffectEvent)
---
1. Re-render Optimizations
Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.
1.1 Defer State Reads
Read searchParams/localStorage directly in callbacks instead of subscribing. View detailed examples
1.2 Extract to Memoized Components
Move expensive work into memoized components for early bailout. View detailed examples
1.3 Narrow Effect Dependencies
Use primitive dependencies (id) instead of objects (user) in useEffect. View detailed examples
1.4 Subscribe to Derived State
Subscribe to boolean state (isMobile) instead of continuous values (width). View detailed examples
1.5 Use Functional setState Updates
Use setState(curr => ...) to avoid stale closures and unstable callbacks. View detailed examples
1.6 Use Lazy State Initialization
Use useState(() => expensive()) to avoid re-running initializers. View detailed examples
1.7 Use Transitions for Non-Urgent Updates
Wrap frequent, non-urgent updates in startTransition() to keep UI responsive. View detailed examples
1.8 Calculate Derived State During Rendering
Compute values from props/state during render instead of storing in state or syncing via effects. View detailed examples
1.9 Avoid useMemo For Simple Expressions
Skip useMemo for simple primitives (booleans, numbers, strings). View detailed examples
1.10 Extract Default Non-primitive Parameter Value
Move default object/array/function parameters to constants to preserve memo() optimization. View detailed examples
1.11 Put Interaction Logic in Event Handlers
Run user-triggered side effects (submit, click) in handlers, not state + effect combos. View detailed examples
1.12 Use useRef for Transient Values
Store frequently-changing non-UI values (mouse position, intervals) in refs to avoid re-renders. View detailed examples
1.13 Don't Define Components Inside Components
Define components at module scope, not inside other components to prevent remounting. View detailed examples
1.14 Split Combined Hook Computations
Separate hooks with independent dependencies to avoid unnecessary recomputation. View detailed examples
1.15 Use useDeferredValue for Expensive Derived Renders
Keep user input responsive while deferring expensive computations or renders. View detailed examples
---
2. Rendering Performance
Optimizing the rendering process reduces the work the browser needs to do.
2.1 Animate SVG Wrapper Instead of SVG Element
Wrap SVG in a div and animate the wrapper for GPU acceleration. View detailed examples
2.2 CSS content-visibility for Long Lists
Apply content-visibility: auto to defer off-screen rendering in long lists. View detailed examples
2.3 Hoist Static JSX Elements
Extract static JSX to module scope to avoid recreating on every render. View detailed examples
2.4 Optimize SVG Precision
Reduce SVG coordinate precision to 1 decimal place with SVGO. View detailed examples
2.5 Prevent Hydration Mismatch Without Flickering
Use inline <script> to sync client-side values before React hydrates. View detailed examples
2.6 Use Activity Component for Show/Hide
Use <Activity mode="visible|hidden"> to preserve state when toggling visibility. View detailed examples
2.7 Hoist RegExp Creation
Create RegExp at module scope or memoize with useMemo to avoid re-creation. View detailed examples
2.8 Use useTransition Over Manual Loading States
Use built-in useTransition with isPending instead of manual loading state management. View detailed examples
---
3. Advanced Patterns
3.1 Store Event Handlers in Refs
Use useEffectEvent (React 19.2+) to prevent effect re-subscriptions. View detailed examples
3.2 useLatest for Stable Callback Refs
Access latest values in callbacks without adding to dependency arrays. Prefer useEffectEvent for React 19.2+. View detailed examples
3.3 Cache Repeated Function Calls
Use module-level Map cache for expensive computations called repeatedly. View detailed examples
3.4 Initialize App Once, Not Per Mount
Use module-level guards for app-wide initialization instead of component useEffect. View detailed examples
3.5 Do Not Put Effect Events in Dependency Arrays
Never include useEffectEvent functions in dependency arrays — depend on reactive values instead. View detailed examples
---
4. Misc
4.1 Named Imports
Always use named imports from 'react', not default or wildcard imports. View detailed examples
4.2 No forwardRef
Use ref as a prop instead of forwardRef (deprecated in React 19). View detailed examples
4.3 React Compiler Guide
Understand what React Compiler optimizes automatically vs manual optimizations still needed. View detailed guide
4.4 Quick Reference Checklists
Checklists for common scenarios: new components, performance reviews, SSR, React 19 migration, etc. View checklists
4.5 Compound Pattern Examples
Real-world examples showing multiple optimization patterns working together. View compound patterns
╭───────────────────────────────╮ │ accelint-react-best-practices │ ╰───────────────────────────────╯
<!-- Make sure to display this warning block to the user --> ┌──────────────────────────────────────────────────────┐ │ ⚠️ WARNING: This skill does it's best to process │ │ the context needed to suggest correct best practices │ │ but it can make mistakes. Please make sure to read │ │ the summary section of each issue to make sure it │ │ isn't a false positive. │ └──────────────────────────────────────────────────────┘
Report: [Target Name]
<!-- INSTRUCTIONS FOR COMPLETING THIS TEMPLATE:
1. Replace [Target Name] with the specific file/module being audited (e.g., "User Authentication", "Data Processing Utils")
2. EXECUTIVE SUMMARY: Provide a high-level overview
- Summarize what was audited and the scope
- Count issues by severity and category
- Include Impact Assessment explaining the potential risks and maintainability concerns
3. PHASE 1 - ISSUE GROUPING RULES:
- Group issues when they share the SAME root cause AND same fix pattern
- Example: Multiple instances of missing memoization → group together
- Example: Different safety violations → separate issues
- Use subsections (4-8) for grouped issues, individual numbers (1, 2, 3) for unique issues
4. PHASE 1 - EACH ISSUE/GROUP MUST INCLUDE:
- Location (file:line or file:line-range)
- Current code with ❌ marker
- Clear explanation of the issue
- Severity (Critical, High, Medium, Low)
- Category (Derived State, Safety, State Management, Hoisting Static JSX, Code Quality, etc...)
- Impact (potential bugs, maintainability concerns, runtime failures)
- Pattern Reference (which references/*.md file) + inline examples from that reference
- ❌ Anti-pattern Example (from the reference file showing typical bad code)
- ✅ Correct Pattern (from the reference file showing typical good code)
- Recommended Fix for This Code with ✅ marker (applying the pattern to their specific code)
5. SEVERITY LEVELS:
- Critical: Could cause infinite re-renders, memory leaks, hydration failures, app crashes
Examples: missing useCallback in effect dependencies, infinite effect loops, client-only state in SSR
- High: Causes frequent unnecessary re-renders, stale closure bugs, or significant performance degradation
Examples: inline object/array creation in props, missing memoization on expensive computations, effect dependency issues
- Medium: Suboptimal patterns affecting performance or maintainability
Examples: large static JSX not hoisted, missing content-visibility on long lists, unnecessary useMemo
- Low: Minor optimizations and style preferences
Examples: could use lazy initialization, could extract component for clarity
6. CATEGORIES:
- Re-render Optimization: Unnecessary re-renders, missing memoization, inline object creation
- Hooks: useEffect dependencies, stale closures, infinite loops, useCallback/useMemo usage
- State Management: Derived state, functional updates, state initialization, ref usage
- Performance: Bundle size (static JSX hoisting, SVG optimization), rendering performance (content-visibility)
- Hydration: SSR/SSG mismatches, client-only state, synchronization issues
- React 19: forwardRef deprecation, named imports, Activity component, useEffectEvent
- Code Quality: Component extraction, naming conventions, readability
7. IMPACT FIELD SHOULD DESCRIBE:
- User experience impact (UI jank, input lag, slow page loads)
- Re-render frequency and performance degradation
- Memory leaks or infinite loops that could crash the app
- Hydration mismatches that break SSR/SSG
- Bundle size impact on load time
- Stale closure bugs that cause incorrect behavior
- Maintainability concerns for future developers
8. PHASE 2: Generate summary table from Phase 1 findings
- Include all issues with their numbers
- Keep it concise - one row per issue/group
See assets/audit-report-example.md for a real-world example. -->
Executive Summary
Completed systematic audit of [file/module path] following accelint-react-best-practices standards. Identified [N] performance and correctness issues across [N] severity levels. [Brief description of what this component/feature does and why performance matters].
Key Findings:
- [N] Critical issues (infinite re-renders, memory leaks, hydration mismatches)
- [N] High severity issues (unnecessary re-renders, stale closures, effect dependency problems)
- [N] Medium severity issues (suboptimal patterns, bundle size concerns)
- [N] Low severity issues (minor optimizations)
Impact Assessment: [Explain the overall performance profile and user experience concerns. Consider:]
- What are the potential runtime failures or performance degradation issues?
- How do unnecessary re-renders affect user experience (UI jank, input lag)?
- What hydration mismatches or SSR issues exist?
- Are there memory leaks in effects or subscriptions?
- How do these issues affect bundle size, load time, and interactivity?
---
Phase 1: Identified Issues
1. [Function/Location] - [Issue Type]
Location: [file:line] or [file:line-range]
// ❌ Current: [Brief description of problem]
[code snippet showing the issue]Issue:
- [Point 1 explaining the problem]
- [Point 2 with specifics about the violation]
- [Point 3 quantifying the impact if possible]
Severity: [Critical|High|Medium|Low] Category: [Re-render Optimization|Hooks|State Management|Performance|Hydration|React 19|Code Quality] Impact:
- User experience: [UI jank, input lag, slow renders]
- Re-renders: [How often and why unnecessary re-renders occur]
- Performance: [Bundle size, memory, runtime performance impact]
- Correctness: [Stale closures, hydration mismatches, infinite loops]
Pattern Reference: [filename.md]
❌ Anti-pattern Example:
// [Inline example from the reference showing the problem]
[typical bad code]✅ Correct Pattern:
// [Inline example from the reference showing the solution]
[typical good code]Recommended Fix for This Code:
// ✅ [Brief description of solution]
[code snippet showing the fix]---
2. [Function/Location] - [Issue Type]
Location: [file:line] or [file:line-range]
// ❌ Current: [Brief description of problem]
[code snippet]Issue:
- [Explanation]
Severity: [Critical|High|Medium|Low] Category: [Re-render Optimization|Hooks|State Management|Performance|Hydration|React 19|Code Quality] Impact:
- User experience: [UI jank, input lag, slow renders]
- Re-renders: [How often and why unnecessary re-renders occur]
- Performance: [Bundle size, memory, runtime performance impact]
- Correctness: [Stale closures, hydration mismatches, infinite loops]
Pattern Reference: [filename.md]
❌ Anti-pattern Example:
// [Inline example from the reference showing the problem]
[typical bad code]✅ Correct Pattern:
// [Inline example from the reference showing the solution]
[typical good code]Recommended Fix for This Code:
// ✅ [Brief description of solution]
[code snippet]---
3-N. [Grouped Issues] - [Shared Issue Type] ([N] instances)
<!-- Use this format when multiple issues share the same root cause and fix pattern -->
Locations:
[file:line]- [function/context][file:line]- [function/context][file:line]- [function/context]
Example from [specific location]:
// ❌ Current: [Brief description of problem]
[representative code snippet]Issue:
- [Shared root cause explanation]
- [Why this pattern is problematic]
- [Impact across all instances]
Severity: [Critical|High|Medium|Low] Category: [Re-render Optimization|Hooks|State Management|Performance|Hydration|React 19|Code Quality] Impact:
- User experience: [UI jank, input lag, slow renders across all instances]
- Re-renders: [How often and why unnecessary re-renders occur]
- Performance: [Bundle size, memory, runtime performance impact]
- Correctness: [Stale closures, hydration mismatches, infinite loops]
Pattern Reference: [filename.md]
❌ Anti-pattern Example:
// [Inline example from the reference showing the problem]
[typical bad code]✅ Correct Pattern:
// [Inline example from the reference showing the solution]
[typical good code]Recommended Fix for This Code:
// ✅ [Brief description of solution]
[fixed code snippet]Same pattern applies to all [N] instances:
// [Location/function 2]
// ❌ Current
[code snippet]
// ✅ Better
[fixed snippet]
// [Location/function 3]
// ❌ Current
[code snippet]
// ✅ Better
[fixed snippet]---
Phase 2: Categorized Issues
| # | Location | Issue | Category | Severity |
|---|---|---|---|---|
| 1 | [file:line] | [Brief issue description] | [Category] | [Severity] |
| 2 | [file:line] | [Brief issue description] | [Category] | [Severity] |
| 3 | [file:line] | [Brief issue description] | [Category] | [Severity] |
| 4-N | [multiple] | [Brief issue description] | [Category] | [Severity] |
Total Issues: [N] By Severity: Critical ([N]), High ([N]), Medium ([N]), Low ([N]) By Category: [Category1] ([N]), [Category2] ([N]), [Category3] ([N])
Changelog
[1.8.0] - 2026-05-18
Changed
- Updated output report template to inline examples from reference files
- Rationale: User feedback requested inline examples instead of just links for better self-contained audit reports (useful in GitHub PR reviews and Claude Code audits)
- Template now includes: ❌ Anti-pattern Example, ✅ Correct Pattern, and Recommended Fix for This Code sections
- Each issue shows typical bad code, typical good code, then applies pattern to user's specific code
Evaluation Results
- Iteration 2: 100% pass rate (8/8 tests, 24/24 assertions) - Grade A
- Improvement: +12.5% over iteration-1 (87.5% → 100%)
- todolist-audit now passes with comprehensive severity categorization
- All test cases demonstrate strong pattern recognition across React anti-patterns
- Average time: 38.6s (vs 31.4s in iter-1, increase from more thorough audits)
- Average tokens: 23,609 (stable, similar to iter-1's 23,268)
Version
- Bumped from 1.7.0 → 1.8.0
[1.7.0] - 2026-05-18
Changed
- CRITICAL FIX: Moved React Compiler check to the top of both SKILL.md and AGENTS.md
- Rationale: Agents were suggesting manual memoization without first checking if React Compiler is enabled. Compiler awareness was buried in "Important Notes" section (line 160+), causing agents to miss it.
- Impact: Agents will now check for React Compiler first before suggesting memo/useMemo/useCallback optimizations
Added
- New "Before Optimizing Performance, Ask" section in SKILL.md with 3-step checklist
- Does project use React Compiler?
- Is this actually a performance problem?
- What's the scale?
- Prominent "⚡ FIRST: Check React Compiler" section at top of AGENTS.md
Evaluation Results
- Iteration 1: 100% pass rate (8/8 tests) after fixing permission issues
- Key strengths: React-specific terminology, modern patterns (useEffectEvent, useDeferredValue), multiple solution approaches
- Trade-off: 50% more time, 31% more tokens vs baseline, but significantly better explanations and depth
Version
- Bumped from 1.6.0 → 1.7.0
[1.6.0] - 2026-05-18
Added
- New Advanced Pattern reference: effect-event-deps.md
- Pattern 3.5: Do Not Put Effect Events in Dependency Arrays
- Explains why Effect Event functions have unstable identity and must not be in deps arrays
- Includes ❌/✅ examples showing incorrect (with handleConnected in deps) vs correct (only reactive values)
- React Compiler note confirming manual optimization required
- Rationale: Critical guidance for React 19.2+ useEffectEvent adoption — common mistake to treat Effect Events like regular callbacks
Changed
- Updated SKILL.md to reference new effect-event-deps.md in Advanced Patterns section
- Updated AGENTS.md with 3.5 entry and one-line summary
- Formatted effect-event-deps.md with numbered title (3.5) to match existing reference structure
Version
- Bumped from 1.5 → 1.6
[1.5.0] - 2026-03-19
Added
- New re-render optimization references for advanced hook patterns
split-combined-hooks.md- Split hooks with independent dependencies to avoid unnecessary recomputationuse-deferred-value.md- Use useDeferredValue to keep input responsive during expensive renders- Rationale: These patterns address common performance issues with combined hooks and expensive derived state
- Enhanced Quick Diagnostic Guide in AGENTS.md
- Added "Hook runs expensive computation unnecessarily → 1.14 Split Combined Hook Computations"
- Added "useDeferredValue" as alternative to "Typing/input feels sluggish"
- Expanded trigger keywords in frontmatter description
- Added "useDeferredValue, combined hooks" for better skill activation
Changed
- Updated SKILL.md re-render optimizations section
- Added references to split-combined-hooks.md and use-deferred-value.md
- Positioned under existing re-render optimizations, before "Rendering Performance"
- Updated AGENTS.md with new pattern entries
- 1.14 Split Combined Hook Computations
- 1.15 Use useDeferredValue for Expensive Derived Renders
- Updated compound-patterns.md to integrate new patterns
- Example 1 (Search Component): Added useDeferredValue as alternative approach to useTransition with comparison guide
- Example 4 (Form Validation): Explicitly called out 1.14 Split Combined Hook pattern, which was already demonstrated but not labeled
- Added inline comments clarifying where split-combined-hooks pattern is applied
Version
- Bumped from 1.4 → 1.5
[1.4.0] - 2026-03-18
Added
- New "Using Skill Patterns Appropriately" section to improve flexibility
- Encourages presenting reference patterns while mentioning alternative approaches
- Guides agents to consider user's React version, project complexity, and team preferences
- Suggests simpler solutions for basic cases even when not in reference files
- Example: SSR hydration can use mounted flag pattern for simple cases vs synchronous script
- Rationale: Evaluation showed skill could be overly prescriptive by only suggesting one solution from reference files
Version
- Bumped from 1.3 → 1.4
[1.3.0] - 2026-03-18
Changed - Structural Improvements
- CRITICAL FIX: Removed 80 lines of activation knowledge from SKILL.md body (lines 14-94)
- "When to Activate This Skill" section → moved to description only
- "When NOT to Use This Skill" section → moved to description only
- "Example Trigger Phrases" section → moved to description only
- Rationale: Activation knowledge belongs ONLY in frontmatter description, not skill body
- Added "NEVER Do React" section with 8 critical anti-patterns and expert reasoning
- Inline component definitions causing remounts
- Unnecessary subscriptions to searchParams/localStorage
- Object/array dependencies in effects
- useState + useEffect for derived state
- Client-only state in SSR causing hydration mismatches
- Deprecated forwardRef usage
- Inline props breaking memoization
- User interaction logic in effects
- Enhanced description to be more "pushy" about triggering
- Added "ALWAYS use this skill when working with any React code"
- Expanded trigger keywords: useEffect, useState, useMemo, useCallback, memo, SSR, Next.js
- Increased from 344 chars to 640 chars for better coverage
Fixed
- Corrected skill name reference in SKILL.md line 168 (
/accelint-ts-best-practices→/accelint-react-best-practices) - Author metadata confirmed as "accelint" (consistent across all files)
Added
- Created comprehensive evaluation test suite (
evals/evals.json) - 8 realistic test prompts covering all major React patterns
- Inline component focus loss debugging
- Infinite effect loop resolution
- SSR hydration mismatch fixes
- Performance optimization for large datasets
- Long list rendering optimization
- Stale closure bugs
- React 19 migration patterns
- Effect re-subscription issues
Version
- Bumped from 1.2 → 1.3
[1.2.0] - Previous
- Initial comprehensive React best practices skill
- 30+ optimization patterns across re-renders, effects, SSR, React 19
- Progressive disclosure structure with AGENTS.md + references/
- React Compiler awareness guide
- Quick reference checklists
- Helper detection scripts
Test Assertions for accelint-react-best-practices
Eval 1: Inline Component Focus Loss
What to check:
- Component extracted to module scope (not inside parent)
- Props passed down (value, onChange, theme)
- No nested component definition
Assertions: 1. SearchInput defined outside SearchBar function 2. SearchInput receives props (value, onChange, theme) 3. Explanation mentions "inline component" or "component inside component" anti-pattern
Eval 2: Infinite Effect Loop
What to check:
- Dependency changed from [user] to [userId]
- Explanation mentions object reference causing re-trigger
- No other unnecessary changes
Assertions: 1. useEffect dependency is [userId] not [user] 2. Explanation mentions dependency array issue 3. Explanation mentions primitive vs object reference
Eval 3: SSR Hydration Mismatch
What to check:
- Solution prevents server/client HTML mismatch
- Either: mounted flag + useEffect, OR synchronous script pattern
- localStorage only read on client side
Assertions: 1. Initial state is consistent (no typeof window check in useState) 2. Either has mounted flag + useEffect, OR mentions synchronous script 3. Explanation mentions hydration mismatch
Eval 4: Chart Performance Freeze
What to check:
- chartData wrapped in useMemo
- Dependencies specified correctly [data, chartType or similar]
- Optional: useTransition for non-urgent updates
Assertions: 1. chartData creation wrapped in useMemo 2. Explanation mentions inline object recreation issue 3. Optional: mentions useTransition for view switching
Eval 5: TodoList Audit
What to check:
- Identifies inline event handler creation
- Mentions CSS content-visibility for long scrollable list
- Suggests extracting memoized TodoItem component
Assertions: 1. Mentions inline function creation in render 2. Suggests content-visibility CSS or virtualization 3. Suggests extracting TodoItem or memoization
Eval 6: Stale Closure Chat
What to check:
- Uses functional setState: setUnreadCount(curr => curr + 1)
- Same for setMessages: setMessages(prev => [...prev, msg])
- Explanation mentions stale closure issue
Assertions: 1. setUnreadCount uses functional update (curr/prev => curr + 1) 2. setMessages uses functional update (prev => [...prev, msg]) 3. Explanation mentions stale closure or callback dependency issue
Eval 7: React 19 Migration
What to check:
- Removes forwardRef wrapper
- Uses ref as regular prop
- Changes to named import from 'react'
Assertions: 1. No forwardRef usage 2. ref passed as regular prop ({ ref }) 3. Uses named import: import { ... } from 'react'
Eval 8: WebSocket Reconnect Loop
What to check:
- Stabilizes handleMessage callback
- Either: useCallback with proper deps, OR ref, OR useEffectEvent
- Explanation mentions unstable function reference
Assertions: 1. handleMessage stabilized (useCallback/ref/useEffectEvent) 2. Effect dependencies correct 3. Explanation mentions function reference stability issue
{
"skill_name": "accelint-react-best-practices",
"evals": [
{
"id": 1,
"prompt": "Hey, I'm having this weird issue where my SearchInput component loses focus every time I type a character. It's driving me crazy - I have to click back into the field after every keystroke. The component is defined inside my SearchBar parent component because I need access to the `theme` prop. Can you figure out what's wrong? Here's the relevant code:\n\nfunction SearchBar({ theme, onSearch }) {\n const [query, setQuery] = useState('')\n \n const SearchInput = () => (\n <input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n className={theme === 'dark' ? 'input-dark' : 'input-light'}\n />\n )\n \n return (\n <div>\n <SearchInput />\n <button onClick={() => onSearch(query)}>Search</button>\n </div>\n )\n}",
"expected_output": "Should identify inline component definition anti-pattern and refactor SearchInput to module scope with props",
"files": [],
"assertions": [
{
"name": "Component extracted to module scope",
"check": "SearchInput is defined outside the SearchBar function"
},
{
"name": "Props passed correctly",
"check": "SearchInput receives value, onChange, and theme as props"
},
{
"name": "Identifies anti-pattern",
"check": "Explanation mentions 'inline component', 'component inside component', 'nested component', or 'remount' issue"
}
]
},
{
"id": 2,
"prompt": "My useEffect is running infinitely and I can't figure out why. I'm trying to fetch user data when the user object changes. Here's my code:\n\nfunction UserProfile({ userId }) {\n const [user, setUser] = useState(null)\n const [loading, setLoading] = useState(false)\n \n useEffect(() => {\n setLoading(true)\n fetch(`/api/users/${userId}`)\n .then(r => r.json())\n .then(data => setUser(data))\n .finally(() => setLoading(false))\n }, [user])\n \n if (loading) return <div>Loading...</div>\n return <div>{user?.name}</div>\n}\n\nThe console is spamming fetch requests. What am I doing wrong?",
"expected_output": "Should identify effect dependency issue (user changes on every fetch), recommend using userId as dependency instead of user object",
"files": [],
"assertions": [
{
"name": "Correct dependency array",
"check": "useEffect dependency is [userId], not [user]"
},
{
"name": "Identifies dependency issue",
"check": "Explanation mentions dependency array, infinite loop, or effect triggering itself"
},
{
"name": "Explains primitive vs object",
"check": "Mentions primitive values vs object references or similar concept"
}
]
},
{
"id": 3,
"prompt": "I'm getting hydration mismatch errors in my Next.js app. I have a theme switcher that reads from localStorage but it keeps showing this warning about server/client mismatch and there's a flash of the wrong theme on page load. Here's my ThemeProvider:\n\nfunction ThemeProvider({ children }) {\n const [theme, setTheme] = useState(\n typeof window !== 'undefined' \n ? localStorage.getItem('theme') || 'light'\n : 'light'\n )\n \n return (\n <ThemeContext.Provider value={{ theme, setTheme }}>\n <div className={theme}>{children}</div>\n </ThemeContext.Provider>\n )\n}\n\nHow do I fix the hydration error and the flash?",
"expected_output": "Should identify SSR hydration mismatch pattern and recommend synchronous inline script solution to sync state before React hydrates",
"files": [],
"assertions": [
{
"name": "Consistent initial state",
"check": "Initial useState value is consistent (no typeof window check in useState call)"
},
{
"name": "Client-side solution",
"check": "Uses mounted flag + useEffect OR synchronous script OR suppressHydrationWarning pattern"
},
{
"name": "Identifies hydration mismatch",
"check": "Explanation mentions 'hydration mismatch', 'SSR', or 'server/client mismatch'"
}
]
},
{
"id": 4,
"prompt": "our analytics dashboard is super slow when rendering the chart with 5000+ data points. I'm using react-chartjs and the whole page freezes for like 2 seconds when you switch between different chart views. here's the component:\n\nfunction ChartView({ data, chartType }) {\n const chartData = {\n labels: data.map(d => d.date),\n datasets: [{\n label: 'Revenue',\n data: data.map(d => d.revenue),\n borderColor: '#3b82f6',\n backgroundColor: 'rgba(59, 130, 246, 0.1)'\n }]\n }\n \n return (\n <div>\n <select onChange={(e) => /* switch chartType */}>\n <option>Line</option>\n <option>Bar</option>\n </select>\n <Chart type={chartType} data={chartData} />\n </div>\n )\n}\n\nany ideas on how to make this faster? the UI feels completely frozen when switching views.",
"expected_output": "Should identify inline object creation problem (chartData recreated every render) and recommend useMemo, also suggest useTransition for non-urgent updates during chart type switching",
"files": [],
"assertions": [
{
"name": "Memoizes chart data",
"check": "chartData wrapped in useMemo with appropriate dependencies"
},
{
"name": "Identifies inline object issue",
"check": "Explanation mentions inline object creation, recreation on every render, or reference stability"
},
{
"name": "Suggests transitions (bonus)",
"check": "Mentions useTransition or useDeferredValue for non-blocking updates"
}
]
},
{
"id": 5,
"prompt": "Review this TodoList component for performance issues. It's part of a task management app and users complain it feels sluggish when they have more than 100 todos. Can you audit it and tell me what optimizations would help?\n\nfunction TodoList({ todos, onToggle, onDelete }) {\n const [filter, setFilter] = useState('all')\n \n const filteredTodos = todos.filter(todo => {\n if (filter === 'active') return !todo.completed\n if (filter === 'completed') return todo.completed\n return true\n })\n \n return (\n <div>\n <div>\n <button onClick={() => setFilter('all')}>All</button>\n <button onClick={() => setFilter('active')}>Active</button>\n <button onClick={() => setFilter('completed')}>Completed</button>\n </div>\n <ul style={{ height: '600px', overflow: 'auto' }}>\n {filteredTodos.map(todo => (\n <li key={todo.id}>\n <input\n type=\"checkbox\"\n checked={todo.completed}\n onChange={() => onToggle(todo.id)}\n />\n <span>{todo.text}</span>\n <button onClick={() => onDelete(todo.id)}>Delete</button>\n </li>\n ))}\n </ul>\n </div>\n )\n}",
"expected_output": "Should identify missing CSS content-visibility optimization for long scrollable list, inline event handler creation, and potential for extracting memoized TodoItem component",
"files": [],
"assertions": [
{
"name": "Identifies inline functions",
"check": "Mentions inline function creation in map or event handlers being recreated"
},
{
"name": "Suggests list optimization",
"check": "Mentions content-visibility CSS, virtualization, or windowing for long lists"
},
{
"name": "Suggests component extraction",
"check": "Suggests extracting TodoItem component with memo() or similar optimization"
}
]
},
{
"id": 6,
"prompt": "I'm seeing a weird bug where my callback always has stale state. I have a real-time chat app and when a new message comes in via WebSocket, my handleNewMessage callback always uses the old `unreadCount` value, not the latest one. Here's the simplified code:\n\nfunction ChatRoom({ roomId }) {\n const [messages, setMessages] = useState([])\n const [unreadCount, setUnreadCount] = useState(0)\n \n const handleNewMessage = useCallback((msg) => {\n setMessages([...messages, msg])\n if (!document.hasFocus()) {\n setUnreadCount(unreadCount + 1) // This always uses the old value!\n }\n }, [roomId])\n \n useEffect(() => {\n const ws = new WebSocket(`/ws/${roomId}`)\n ws.onmessage = (e) => handleNewMessage(JSON.parse(e.data))\n return () => ws.close()\n }, [roomId, handleNewMessage])\n \n return <div>{/* render messages */}</div>\n}\n\nEven though unreadCount changes, the callback seems frozen with the old value. What's happening?",
"expected_output": "Should identify stale closure issue and recommend functional setState update pattern: setUnreadCount(curr => curr + 1) and fixing messages state update similarly",
"files": [],
"assertions": [
{
"name": "Uses functional setState for unreadCount",
"check": "setUnreadCount uses functional update: (curr/prev => curr + 1)"
},
{
"name": "Uses functional setState for messages",
"check": "setMessages uses functional update: (prev => [...prev, msg])"
},
{
"name": "Identifies stale closure",
"check": "Explanation mentions 'stale closure', 'captured values', or 'callback dependencies'"
}
]
},
{
"id": 7,
"prompt": "need to migrate this component to React 19 - I'm getting deprecation warnings about forwardRef. how do I fix this?\n\nconst TextInput = forwardRef(function TextInput({ label, ...props }, ref) {\n return (\n <div>\n <label>{label}</label>\n <input ref={ref} {...props} />\n </div>\n )\n})\n\nexport default TextInput\n\nAlso seeing warnings about default imports from React. What's the new pattern?",
"expected_output": "Should convert forwardRef to ref-as-prop pattern and change default export to named import from 'react'",
"files": [],
"assertions": [
{
"name": "Removes forwardRef",
"check": "No forwardRef usage in the code"
},
{
"name": "Ref as prop",
"check": "ref is accepted as a regular prop in function signature: { ref }"
},
{
"name": "Named imports",
"check": "Uses named imports from 'react' (import { ... } from 'react') or no import if not needed"
}
]
},
{
"id": 8,
"prompt": "I have this subscription hook that's causing my component to re-subscribe to the WebSocket on every single render. The effect runs constantly and I see disconnect/reconnect spam in the console. Code:\n\nfunction useRealtimeData(userId) {\n const [data, setData] = useState(null)\n \n const handleMessage = (msg) => {\n setData(msg.data)\n console.log('Received update for user', userId)\n }\n \n useEffect(() => {\n const ws = new WebSocket('/api/realtime')\n ws.onmessage = handleMessage\n console.log('Connected')\n \n return () => {\n ws.close()\n console.log('Disconnected')\n }\n }, [handleMessage])\n \n return data\n}\n\nThe logs show connect/disconnect cycling non-stop. I thought adding handleMessage to dependencies was the right thing to do?",
"expected_output": "Should identify unstable callback causing effect re-runs and recommend either useCallback with proper deps, store handler in ref, or useEffectEvent pattern (React 19.2+)",
"files": [],
"assertions": [
{
"name": "Stabilizes callback",
"check": "handleMessage is either: moved inside effect, wrapped in useCallback, stored in ref, or uses useEffectEvent"
},
{
"name": "Correct effect dependencies",
"check": "Effect dependencies only include userId or stable values, not the unstable handleMessage"
},
{
"name": "Identifies function reference issue",
"check": "Explanation mentions function reference stability, recreated function, or dependency causing re-runs"
}
]
}
]
}
React Best Practices
Comprehensive performance optimization and best practices for React applications, designed for AI agents and LLMs working with React code.
Overview
This skill provides structured guidance for React performance optimization, covering:
- Re-render optimizations
- Rendering performance improvements
- Advanced patterns for state and effects
- React 19+ migration guidance
- React Compiler awareness
A large number of these patterns were originally from Vercel's Skill and have been expanded with additional patterns, React Compiler guidance, and comprehensive examples.
Note: This skill focuses on React-specific optimizations. Meta-framework specific optimizations (Next.js, Remix, etc.) are not included.
---
Quick Start
For Agents/LLMs
1. Read [SKILL.md](SKILL.md) - Understand when to activate this skill and how to use it 2. Reference [AGENTS.md](AGENTS.md) - Browse rule summaries with Quick Diagnostic Guide and Priority Matrix 3. Load specific patterns - Access detailed examples in references/ as needed 4. Use checklists - Apply quick-checklists.md for systematic reviews
For Humans
This skill is optimized for AI agents but humans may find it useful for:
- Learning React performance optimization patterns
- Reviewing code for common anti-patterns
- Understanding React 19+ features and migrations
- Systematic performance auditing with checklists
---
Pattern Categories
1. Re-render Optimizations
Patterns to reduce unnecessary component re-renders and state updates:
- Defer state reads
- Extract to memoized components
- Narrow effect dependencies
- Subscribe to derived state
- Functional setState updates
- Lazy state initialization
- Transitions for non-urgent updates
2. Rendering Performance
Patterns to optimize actual rendering and painting:
- Animate SVG wrapper (GPU acceleration)
- CSS content-visibility (long lists)
- Hoist static JSX
- Optimize SVG precision
- Prevent hydration mismatch
- Activity component (preserve state)
- Hoist RegExp creation
- Avoid useMemo for simple expressions
3. Advanced Patterns
Specialized patterns for complex scenarios:
- Store event handlers in refs (useEffectEvent)
- useLatest for stable callbacks
- Cache repeated function calls
4. React 19+ Migration
Patterns for React 19 and modern React:
- Named imports only
- No forwardRef (use ref prop)
- React Compiler guide
---
Key Features
Progressive Disclosure
- Start with rule summaries in AGENTS.md
- Load detailed examples only when needed
- Minimizes context usage for LLMs
React Compiler Awareness
- Clear guidance on what React Compiler handles automatically
- Standardized notes on all patterns indicating manual vs automatic optimization
- Dedicated React Compiler Guide
Quick Diagnostic Guide
Navigate directly to relevant patterns based on symptoms:
- "Component re-renders too often" → Section 1
- "Scrolling is janky" → Section 2.2, 2.1
- "Hydration mismatch errors" → Section 2.5
Comprehensive Checklists
Ready-to-use checklists for:
- New component creation
- Performance reviews
- SSR/SSG projects
- Effect debugging
- React 19 migration
- Bundle size optimization
- Code reviews
Real-World Examples
Compound Patterns shows complete examples:
- Optimized search component
- Infinite scroll list
- Dashboard with widgets
- Form with validation
- SSR dashboard with theme
---
React 19 Support
This skill covers React 19+ features including:
useEffectEvent(19.2+) for stable event handlers<Activity>component for preserving hidden component staterefas a prop (replaces deprecatedforwardRef)- Named imports only (no default import of React)
Resources:
---
Usage in Claude Code
This skill is designed to be used with environments such as Claude Code (claude.ai/claude-code) and automatically activates when:
- Writing React components, hooks, or JSX
- Refactoring React code
- Optimizing re-renders or performance
- Reviewing React code
- Fixing hydration mismatches
- Implementing React 19 features
See SKILL.md for complete activation criteria and trigger phrases.
---
Contributing
When adding new patterns:
1. Create reference file in references/ following the standard format:
- Clear title and one-line summary
- ❌ Incorrect example(s) showing the anti-pattern
- ✅ Correct example(s) showing the optimal implementation
- React Compiler Note (handled automatically vs manual required)
- Additional context if needed
2. Add to AGENTS.md with one-line summary and link 3. Update SKILL.md categorization if needed 4. Add to checklists in references/quick-checklists.md 5. Consider compound patterns - Add to references/compound-patterns.md if the pattern commonly combines with others
---
Performance Philosophy
This skill follows these principles:
1. Correctness first - Avoid bugs before optimizing performance 2. Measure before optimizing - Profile to identify real bottlenecks 3. Optimize slowest operations first - Network > rendering > computation 4. Avoid premature optimization - Don't optimize trivial operations 5. Prefer simplicity - Simple, readable code over clever optimizations 6. Document non-obvious patterns - Explain why optimizations exist
---
References
- https://github.com/vercel-labs/agent-skills/tree/main/skills/react-best-practices
- https://github.com/buildworksai/AgentHub/blob/main/.agent/skills/react-best-practices/skill.md
- https://github.com/programming-in-th/programming.in.th/blob/main/.claude/docs/react-patterns.md
- https://github.com/softaworks/agent-toolkit/tree/main/skills/react-dev
- https://github.com/softaworks/agent-toolkit/blob/main/skills/react-useeffect/README.md
- https://github.com/Jeffallan/claude-skills/blob/main/skills/react-expert/SKILL.md
- https://github.com/prowler-cloud/prowler/blob/master/skills/react-19/SKILL.md
2.6 Use Activity Component for Show/Hide
Use React's <Activity> component to preserve state/DOM for expensive components that frequently toggle visibility.
import { Activity } from 'react'
function Dropdown({ isOpen }: Props) {
return (
<Activity mode={isOpen ? 'visible' : 'hidden'}>
<ExpensiveMenu />
</Activity>
)
}Avoids expensive re-renders and state loss.
---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still use the <Activity> component explicitly. The compiler cannot transform conditional rendering into Activity component usage.
See react-compiler-guide.md for more details.
2.1 Animate SVG Wrapper Instead of SVG Element
Many browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a <div> and animate the wrapper instead.
❌ Incorrect: animating SVG directly - no hardware acceleration
function LoadingSpinner() {
return (
<svg
className="animate-spin"
width="24"
height="24"
viewBox="0 0 24 24"
>
<circle cx="12" cy="12" r="10" stroke="currentColor" />
</svg>
)
}✅ Correct: animating wrapper div - hardware accelerated
function LoadingSpinner() {
return (
<div className="animate-spin">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
>
<circle cx="12" cy="12" r="10" stroke="currentColor" />
</svg>
</div>
)
}This applies to all CSS transforms and transitions (transform, opacity, translate, scale, rotate). The wrapper div allows browsers to use GPU acceleration for smoother animations.
---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still wrap SVG elements for animation. This is a DOM structure optimization, not a React code optimization.
See react-compiler-guide.md for more details.
1.9 Avoid useMemo For Simple Expressions
When an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in useMemo. Calling useMemo and comparing hook dependencies may consume more resources than the expression itself.
❌ Incorrect: wasted `useMemo` overhead
function Header({ user, notifications }: Props) {
const isLoading = useMemo(() => {
return user.isLoading || notifications.isLoading
}, [user.isLoading, notifications.isLoading]);
if (isLoading) {
return <Skeleton />
}
return /* ... */;
}✅ Correct: no `useMemo` overhead for simple expression
function Header({ user, notifications }: Props) {
const isLoading = user.isLoading || notifications.isLoading
if (isLoading) {
return <Skeleton />;
}
return /* ... */;
}---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, avoid unnecessary useMemo for simple expressions. While the compiler optimizes memoization, removing unnecessary memoization improves code simplicity and readability.
See react-compiler-guide.md for more details.
3.3 Cache Repeated Function Calls
Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.
❌ Incorrect: redundant computation
function ProjectList({ projects }: { projects: Project[] }) {
return (
<div>
{projects.map(project => {
// slugify() called 100+ times for same project names
const slug = slugify(project.name)
return <ProjectCard key={project.id} slug={slug} />
})}
</div>
)
}✅ Correct: cached results
// Module-level cache
const slugifyCache = new Map<string, string>()
function cachedSlugify(text: string): string {
if (slugifyCache.has(text)) {
return slugifyCache.get(text)!
}
const result = slugify(text)
slugifyCache.set(text, result)
return result
}
function ProjectList({ projects }: { projects: Project[] }) {
return (
<div>
{projects.map(project => {
// Computed only once per unique project name
const slug = cachedSlugify(project.name)
return <ProjectCard key={project.id} slug={slug} />
})}
</div>
)
}Simpler pattern for single-value functions:
let isLoggedInCache: boolean | null = null
function isLoggedIn(): boolean {
if (isLoggedInCache !== null) {
return isLoggedInCache
}
isLoggedInCache = document.cookie.includes('auth=')
return isLoggedInCache
}
// Clear cache when auth changes
function onAuthChange() {
isLoggedInCache = null
}Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
Reference: https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast
---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still implement module-level caching for repeated function calls. The compiler cannot create cross-render, global caches automatically.
See react-compiler-guide.md for more details.
1.8 Calculate Derived State During Rendering
If a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead.
❌ Incorrect: redundant state and effect
function Form() {
const [firstName, setFirstName] = useState('First')
const [lastName, setLastName] = useState('Last')
const [fullName, setFullName] = useState('')
useEffect(() => {
setFullName(firstName + ' ' + lastName)
}, [firstName, lastName])
return <p>{fullName}</p>
}✅ Correct: derive during render
function Form() {
const [firstName, setFirstName] = useState('First')
const [lastName, setLastName] = useState('Last')
const fullName = firstName + ' ' + lastName
return <p>{fullName}</p>
}---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still calculate derived state during render instead of using effects. The compiler cannot infer that state should be derived rather than synchronized.
See react-compiler-guide.md for more details.
Reference: https://react.dev/learn/you-might-not-need-an-effect
Compound Pattern Examples
Real-world scenarios often require multiple optimization patterns working together. This guide shows complete examples of how patterns combine to solve complex performance problems.
---
Example 1: Optimized Search Component
Scenario: A search component that filters a large list of items with debounced input
Patterns Applied:
- 1.5 Functional setState Updates - Stable callbacks
- 1.7 Transitions - Non-urgent search results
- 2.8 useTransition Over Manual Loading - Built-in pending state
- 3.2 useLatest / useEffectEvent - Stable debounce callback (useEffectEvent for React 19.2+)
- 1.1 Defer State Reads - Read URL params on demand
Alternative Approach: 1.15 useDeferredValue can replace useTransition for this use case - see note at end of this example.
❌ Before: Multiple Performance Issues
function SearchComponent({ items }: { items: Item[] }) {
const searchParams = useSearchParams() // ❌ Subscribes to all URL changes
const [query, setQuery] = useState(searchParams.get('q') || '')
const [results, setResults] = useState(items)
// ❌ Callback recreated on every query/results change
const handleSearch = useCallback((newQuery: string) => {
setQuery(newQuery)
// ❌ Blocks UI during expensive filtering
const filtered = items.filter(item =>
item.name.toLowerCase().includes(newQuery.toLowerCase())
)
setResults(filtered) // ❌ Direct state reference, not functional
}, [query, results, items])
// ❌ Effect re-runs on handleSearch changes
useEffect(() => {
const timeout = setTimeout(() => handleSearch(query), 300)
return () => clearTimeout(timeout)
}, [query, handleSearch])
return (
<div>
<input value={query} onChange={e => handleSearch(e.target.value)} />
<ResultsList results={results} />
</div>
)
}✅ After: Optimized with Multiple Patterns
import { useEffectEvent, useTransition } from 'react' // React 19.2+
function SearchComponent({ items }: { items: Item[] }) {
const [query, setQuery] = useState(() => {
// ✅ 1.1: Read URL params on demand, no subscription
const params = new URLSearchParams(window.location.search)
return params.get('q') || ''
})
const [results, setResults] = useState(items)
const [isPending, startTransition] = useTransition() // ✅ 2.8: Built-in pending state
// ✅ 1.5: Stable callback using functional setState
const handleSearch = useCallback((newQuery: string) => {
setQuery(newQuery)
const filtered = items.filter(item =>
item.name.toLowerCase().includes(newQuery.toLowerCase())
)
// ✅ 1.7: Transition for non-urgent results update
startTransition(() => {
setResults(filtered)
})
}, [items]) // Only depends on items, not query or results
// ✅ 3.2: useEffectEvent for stable effect with fresh callback (React 19.2+)
// For React < 19.2, use useLatest hook instead
const handleSearchStable = useEffectEvent(handleSearch)
useEffect(() => {
// ✅ Effect stable, only re-runs when query changes
const timeout = setTimeout(() => handleSearchStable(query), 300)
return () => clearTimeout(timeout)
}, [query]) // Only query dependency
return (
<div>
<input
value={query}
onChange={e => handleSearch(e.target.value)}
/>
{isPending && <span className="loading">Searching...</span>}
<ResultsList results={results} />
</div>
)
}Alternative with useDeferredValue:
For this search use case, useDeferredValue is a more specialized alternative to useTransition:
function SearchComponent({ items }: { items: Item[] }) {
const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)
const filtered = useMemo(
() => items.filter(item => fuzzyMatch(item, deferredQuery)),
[items, deferredQuery]
)
const isStale = query !== deferredQuery
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
<div style={{ opacity: isStale ? 0.7 : 1 }}>
<ResultsList results={filtered} />
</div>
</div>
)
}When to use which:
useDeferredValue: When you have a single expensive computation/render driven by user inputuseTransition: When you have multiple state updates that should be treated as non-urgent
---
Example 2: Optimized Infinite Scroll List
Scenario: An infinite scroll component with dynamic data loading
Patterns Applied:
- 1.4 Subscribe to Derived State - Boolean instead of scroll position
- 2.2 CSS content-visibility - Long list rendering
- 1.7 Transitions - Non-urgent load more
- 3.1 Store Event Handlers - Stable scroll subscription
❌ Before: Performance Issues
function InfiniteList({ loadMore }: Props) {
const [items, setItems] = useState<Item[]>([])
const [scrollY, setScrollY] = useState(0) // ❌ Updates on every pixel
// ❌ Re-subscribes on every loadMore change
useEffect(() => {
const handleScroll = () => {
setScrollY(window.scrollY) // ❌ Continuous updates
const scrollHeight = document.documentElement.scrollHeight
const clientHeight = document.documentElement.clientHeight
// ❌ Blocks UI during data loading
if (scrollY + clientHeight >= scrollHeight - 100) {
loadMore().then(newItems => {
setItems([...items, ...newItems]) // ❌ Direct state reference
})
}
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [loadMore, items, scrollY])
return (
<div className="overflow-y-auto">
{items.map(item => (
<div key={item.id}> {/* ❌ No content-visibility */}
<ItemCard item={item} />
</div>
))}
</div>
)
}✅ After: Optimized with Multiple Patterns
function InfiniteList({ loadMore }: Props) {
const [items, setItems] = useState<Item[]>([])
const [isNearBottom, setIsNearBottom] = useState(false)
// ✅ 3.1: useEffectEvent for stable event handler (React 19.2+)
const onLoadMore = useEffectEvent(() => {
loadMore().then(newItems => {
// ✅ 1.7: Transition for non-urgent update
startTransition(() => {
// ✅ 1.5: Functional setState for correctness
setItems(curr => [...curr, ...newItems])
})
})
})
useEffect(() => {
const handleScroll = () => {
const scrollHeight = document.documentElement.scrollHeight
const scrollY = window.scrollY
const clientHeight = document.documentElement.clientHeight
// ✅ 1.4: Subscribe to derived boolean state
const nearBottom = scrollY + clientHeight >= scrollHeight - 100
setIsNearBottom(nearBottom)
if (nearBottom) {
onLoadMore()
}
}
window.addEventListener('scroll', handleScroll, { passive: true })
return () => window.removeEventListener('scroll', handleScroll)
}, []) // ✅ Stable effect, onLoadMore not in dependencies
return (
<div className="overflow-y-auto h-screen">
{items.map(item => (
<div
key={item.id}
className="item-card" // ✅ 2.2: CSS content-visibility applied
style={{
contentVisibility: 'auto',
containIntrinsicSize: '0 200px'
}}
>
<ItemCard item={item} />
</div>
))}
</div>
)
}---
Example 3: Optimized Dashboard with Multiple Widgets
Scenario: A dashboard with multiple data-heavy widgets that update independently
Patterns Applied:
- 1.2 Extract to Memoized Components - Isolate widget updates
- 2.3 Hoist Static JSX - Loading skeletons
- 1.6 Lazy State Initialization - Expensive initial data
- 3.3 Cache Repeated Function Calls - Data transformation
❌ Before: All Widgets Re-render Together
function Dashboard() {
const [userData, setUserData] = useState(
parseUserData() // ❌ Runs on every render
)
const [analyticsData, setAnalyticsData] = useState(
parseAnalyticsData() // ❌ Runs on every render
)
const [loading, setLoading] = useState(false)
if (loading) {
// ❌ Creates new skeleton on every render
return (
<div>
<div className="skeleton h-40 w-full" />
<div className="skeleton h-40 w-full" />
</div>
)
}
// ❌ formatCurrency called repeatedly for same values
const totalRevenue = formatCurrency(analyticsData.revenue)
const averageOrder = formatCurrency(analyticsData.averageOrder)
return (
<div>
{/* ❌ User widget re-renders when analytics changes */}
<UserWidget data={userData} />
<AnalyticsWidget
revenue={totalRevenue}
averageOrder={averageOrder}
/>
</div>
)
}✅ After: Optimized Widget Isolation
// ✅ 2.3: Hoist static loading skeleton
const loadingSkeleton = (
<div>
<div className="skeleton h-40 w-full" />
<div className="skeleton h-40 w-full" />
</div>
)
// ✅ 3.3: Module-level cache for repeated formatting
const formatCache = new Map<number, string>()
function cachedFormatCurrency(amount: number): string {
if (formatCache.has(amount)) {
return formatCache.get(amount)!
}
const formatted = formatCurrency(amount)
formatCache.set(amount, formatted)
return formatted
}
// ✅ 1.2: Memoized components for independent updates
const UserWidget = memo(function UserWidget({ data }: { data: UserData }) {
return <div>User stats: {data.name}</div>
})
const AnalyticsWidget = memo(function AnalyticsWidget({
revenue,
averageOrder
}: {
revenue: number
averageOrder: number
}) {
// ✅ 3.3: Cached formatting
const formattedRevenue = cachedFormatCurrency(revenue)
const formattedAverage = cachedFormatCurrency(averageOrder)
return (
<div>
<div>Revenue: {formattedRevenue}</div>
<div>Average: {formattedAverage}</div>
</div>
)
})
function Dashboard() {
// ✅ 1.6: Lazy initialization - only runs once
const [userData, setUserData] = useState(() => parseUserData())
const [analyticsData, setAnalyticsData] = useState(() => parseAnalyticsData())
const [loading, setLoading] = useState(false)
if (loading) {
return loadingSkeleton // ✅ Reuses same element
}
return (
<div>
{/* ✅ Widgets only re-render when their own data changes */}
<UserWidget data={userData} />
<AnalyticsWidget
revenue={analyticsData.revenue}
averageOrder={analyticsData.averageOrder}
/>
</div>
)
}---
Example 4: Optimized Form with Real-time Validation
Scenario: A complex form with real-time validation and API calls
Patterns Applied:
- 1.5 Functional setState Updates - Stable form handlers
- 1.11 Interaction Logic in Handlers - Submit logic in handler
- 1.3 Narrow Effect Dependencies - Validation effects
- 1.14 Split Combined Hook Computations - Separate email validation from other form fields
- 3.2 useLatest / useEffectEvent - Async validation (useEffectEvent for React 19.2+)
- 1.7 Transitions - Non-blocking validation results
❌ Before: Unstable Dependencies
function RegistrationForm() {
const [formData, setFormData] = useState({ email: '', username: '', password: '' })
const [errors, setErrors] = useState({})
// ❌ Validates on every formData change (including password changes)
// ❌ This is a combined hook - email validation runs when ANY field changes
useEffect(() => {
validateEmail(formData.email).then(isValid => {
setErrors({ ...errors, email: isValid ? null : 'Invalid email' })
})
}, [formData, errors]) // ❌ Object dependencies
// ❌ Callback recreated on every formData change
const handleSubmit = useCallback(async () => {
const result = await submitForm(formData)
if (result.errors) {
setErrors(result.errors)
}
}, [formData, errors])
return (
<form>
<input
value={formData.email}
onChange={e => setFormData({
...formData, // ❌ Direct state reference
email: e.target.value
})}
/>
{/* ... */}
</form>
)
}✅ After: Optimized with Stable Dependencies
import { useEffectEvent } from 'react' // React 19.2+
function RegistrationForm() {
const [formData, setFormData] = useState({
email: '',
username: '',
password: ''
})
const [errors, setErrors] = useState({})
// ✅ 1.5: Stable callback using functional setState
const updateField = useCallback((field: string, value: string) => {
setFormData(curr => ({ ...curr, [field]: value }))
}, [])
// ✅ 3.2: useEffectEvent for async validation (React 19.2+)
// For React < 19.2, use useLatest hook instead
const updateFieldStable = useEffectEvent(updateField)
// ✅ 1.3 + 1.14: Split combined hook - only email, not whole formData
// Email validation now only runs when email changes, not on username/password changes
useEffect(() => {
const validateAsync = async () => {
const isValid = await validateEmail(formData.email)
// ✅ 1.7: Transition for non-urgent validation result
startTransition(() => {
setErrors(curr => ({
...curr,
email: isValid ? null : 'Invalid email'
}))
})
}
if (formData.email) {
validateAsync()
}
}, [formData.email]) // ✅ Primitive dependency
// ✅ 1.11: Interaction logic in event handler, not effect
// ✅ 1.5: Stable submit handler with functional setState
const handleSubmit = useCallback(async () => {
// Read latest formData inside callback
setFormData(curr => {
submitForm(curr).then(result => {
if (result.errors) {
startTransition(() => {
setErrors(result.errors)
})
}
})
return curr
})
}, [])
return (
<form>
<input
value={formData.email}
onChange={e => updateField('email', e.target.value)}
/>
{errors.email && <span className="error">{errors.email}</span>}
{/* ... */}
<button type="button" onClick={handleSubmit}>Submit</button>
</form>
)
}---
Example 5: Optimized SSR Dashboard with Theme
Scenario: A dashboard that needs to render on server and handle client-side theme without flickering
Patterns Applied:
- 2.5 Prevent Hydration Mismatch - Theme handling
- 1.2 Extract to Memoized Components - Widget isolation
- 2.6 Activity Component - Sidebar state preservation
❌ Before: Hydration Mismatch and Flickering
function Dashboard() {
// ❌ localStorage breaks SSR
const [theme, setTheme] = useState(localStorage.getItem('theme') || 'light')
const [sidebarOpen, setSidebarOpen] = useState(true)
return (
<div className={theme}>
{/* ❌ Sidebar state lost when toggled */}
{sidebarOpen && <Sidebar />}
{/* ❌ All widgets re-render when theme changes */}
<UserStatsWidget />
<AnalyticsWidget />
</div>
)
}✅ After: SSR-Safe with Optimized Rendering
// ✅ 1.2: Memoized widgets don't re-render on theme change
const UserStatsWidget = memo(function UserStatsWidget() {
return <div>User statistics...</div>
})
const AnalyticsWidget = memo(function AnalyticsWidget() {
return <div>Analytics...</div>
})
function Dashboard() {
const [sidebarOpen, setSidebarOpen] = useState(true)
return (
<>
{/* ✅ 2.5: Prevent hydration mismatch with inline script */}
<div id="dashboard-wrapper">
{/* ✅ 2.6: Activity preserves sidebar state when hidden */}
<Activity mode={sidebarOpen ? 'visible' : 'hidden'}>
<Sidebar />
</Activity>
<main>
<UserStatsWidget />
<AnalyticsWidget />
</main>
</div>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
try {
var theme = localStorage.getItem('theme') || 'light';
var el = document.getElementById('dashboard-wrapper');
if (el) el.className = theme;
} catch (e) {}
})();
`,
}}
/>
</>
)
}---
Example 6: Optimized Analytics Tracker with Mouse Position
Scenario: A component that tracks mouse position for analytics without causing re-renders, with proper app initialization
Patterns Applied:
- 1.12 useRef for Transient Values - Mouse position tracking
- 1.8 Calculate Derived State - Derive status from props
- 1.10 Extract Default Parameter - Stable default callbacks
- 3.4 Initialize App Once - Analytics SDK initialization
❌ Before: Re-renders on Mouse Move
function AnalyticsTracker({ onTrack, enabled = true }: Props) {
const [mouseX, setMouseX] = useState(0) // ❌ Renders on every pixel
const [mouseY, setMouseY] = useState(0)
const [isTracking, setIsTracking] = useState(false) // ❌ Derived state
// ❌ Runs on every mount, even in dev strict mode
useEffect(() => {
initAnalyticsSDK() // ❌ Initializes multiple times
}, [])
// ❌ Derives isTracking from enabled in effect
useEffect(() => {
setIsTracking(enabled && mouseX > 0)
}, [enabled, mouseX])
useEffect(() => {
const handleMove = (e: MouseEvent) => {
setMouseX(e.clientX) // ❌ Triggers re-render
setMouseY(e.clientY)
}
window.addEventListener('mousemove', handleMove)
return () => window.removeEventListener('mousemove', handleMove)
}, [])
return (
<div>
{isTracking && (
<TrackingIndicator
x={mouseX}
y={mouseY}
onClick={() => {}} // ❌ New function on every render
/>
)}
</div>
)
}✅ After: Optimized with Transient Values
// ✅ 3.4: Initialize SDK once per app load, not per component mount
let didInit = false
function initOnce() {
if (didInit) return
didInit = true
initAnalyticsSDK()
}
// ✅ 1.10: Extract default callback to constant for stable memo()
const NOOP = () => {}
const TrackingIndicator = memo(function TrackingIndicator({
x,
y,
onClick = NOOP // ✅ Stable default value
}: {
x: number
y: number
onClick?: () => void
}) {
return (
<div
style={{
position: 'fixed',
left: x,
top: y,
pointerEvents: 'none'
}}
>
Tracking
</div>
)
})
function AnalyticsTracker({ onTrack, enabled = true }: Props) {
// ✅ 1.12: useRef for frequently-changing transient values
const mouseXRef = useRef(0)
const mouseYRef = useRef(0)
const indicatorRef = useRef<HTMLDivElement>(null)
// ✅ 1.8: Calculate derived state during render
const isTracking = enabled && mouseXRef.current > 0
useEffect(() => {
initOnce() // ✅ 3.4: Guarded initialization
const handleMove = (e: MouseEvent) => {
// ✅ 1.12: Update refs without triggering re-renders
mouseXRef.current = e.clientX
mouseYRef.current = e.clientY
// Directly update DOM for performance
const indicator = indicatorRef.current
if (indicator && enabled) {
indicator.style.transform = `translate(${e.clientX}px, ${e.clientY}px)`
}
// Send analytics without re-rendering
if (enabled && onTrack) {
onTrack({ x: e.clientX, y: e.clientY })
}
}
window.addEventListener('mousemove', handleMove)
return () => window.removeEventListener('mousemove', handleMove)
}, [enabled, onTrack])
return (
<div>
{isTracking && (
<div
ref={indicatorRef}
style={{
position: 'fixed',
left: 0,
top: 0,
pointerEvents: 'none',
transform: 'translate(0px, 0px)'
}}
>
Tracking
</div>
)}
</div>
)
}---
Key Takeaways
1. Patterns often work together - Real-world optimizations typically combine 3-5 patterns 2. Start with correctness - Functional setState (1.5), narrow dependencies (1.3), and interaction logic in handlers (1.11) prevent bugs 3. Derive, don't sync - Calculate derived state during render (1.8), don't use effects to synchronize it 4. Choose the right state storage - Use useState for UI, useRef for transient values (1.12) 5. Then optimize rendering - Memoization (1.2), transitions (1.7/2.8), and derived state (1.4) 6. Stable references matter - Extract default parameters (1.10) to preserve memo() optimization 7. Initialize wisely - App-level initialization once (3.4), component initialization lazily (1.6) 8. Finally, advanced patterns - useEffectEvent/useLatest (3.2), caching (3.3), and Activity (2.6) 9. SSR requires special care - Hydration mismatch prevention (2.5) is critical 10. React Compiler helps - But state/effect patterns still need manual application 11. React 19.2+ advantages - Use useEffectEvent instead of useLatest for cleaner stable event handlers
Refer to the Quick Checklists for systematic pattern application and React Compiler Guide for compiler-specific guidance.
2.2 CSS content-visibility for Long Lists
Apply content-visibility: auto to defer off-screen rendering.
.message-item {
content-visibility: auto;
contain-intrinsic-size: 0 80px;
}function MessageList({ messages }: { messages: Message[] }) {
return (
<div className="overflow-y-auto h-screen">
{messages.map(msg => (
<div key={msg.id} className="message-item">
<Avatar user={msg.author} />
<div>{msg.content}</div>
</div>
))}
</div>
)
}For 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render).
---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still use CSS content-visibility. This is a CSS optimization, not a React code optimization.
See react-compiler-guide.md for more details.
1.1 Defer State Reads
Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.
❌ Incorrect: subscribes to all searchParams changes
function ShareButton({ chatId }: { chatId: string }) {
const searchParams = useSearchParams()
const handleShare = () => {
const ref = searchParams.get('ref')
shareChat(chatId, { ref })
}
return <button onClick={handleShare}>Share</button>
}✅ Correct: reads on demand, no subscription
function ShareButton({ chatId }: { chatId: string }) {
const handleShare = () => {
const params = new URLSearchParams(window.location.search)
const ref = params.get('ref')
shareChat(chatId, { ref })
}
return <button onClick={handleShare}>Share</button>
}---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still defer state reads when appropriate. The compiler cannot infer that you don't need to subscribe to state changes.
See react-compiler-guide.md for more details.
3.5 Do Not Put Effect Events in Dependency Arrays
Effect Event functions do not have a stable identity. Their identity intentionally changes on every render. Do not include the function returned by useEffectEvent in a useEffect dependency array. Keep the actual reactive values as dependencies and call the Effect Event from inside the effect body or subscriptions created by that effect.
❌ Incorrect: Effect Event added as a dependency
import { useEffect, useEffectEvent } from 'react'
function ChatRoom({ roomId, onConnected }: {
roomId: string
onConnected: () => void
}) {
const handleConnected = useEffectEvent(onConnected)
useEffect(() => {
const connection = createConnection(roomId)
connection.on('connected', handleConnected)
connection.connect()
return () => connection.disconnect()
}, [roomId, handleConnected])
}Including the Effect Event in dependencies makes the effect re-run every render and triggers the React Hooks lint rule.
✅ Correct: depend on reactive values, not the Effect Event
import { useEffect, useEffectEvent } from 'react'
function ChatRoom({ roomId, onConnected }: {
roomId: string
onConnected: () => void
}) {
const handleConnected = useEffectEvent(onConnected)
useEffect(() => {
const connection = createConnection(roomId)
connection.on('connected', handleConnected)
connection.connect()
return () => connection.disconnect()
}, [roomId])
}---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still avoid putting Effect Events in dependency arrays. The compiler cannot infer when to exclude Effect Event functions from deps arrays.
See react-compiler-guide.md for more details.
---
Reference: React useEffectEvent: Effect Event in deps
1.10 Extract Default Non-primitive Parameter Value from Memoized Component to Constant
When memoized component has a default value for some non-primitive optional parameter, such as an array, function, or object, calling the component without that parameter results in broken memoization. This is because new value instances are created on every rerender, and they do not pass strict equality comparison in memo(). To address this issue, extract the default value into a constant.
❌ Incorrect: `onClick` has different values on every rerender
const UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {
// ...
})
// Used without optional onClick
<UserAvatar />✅ Correct: stable default value
const NOOP = () => {};
const UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {
// ...
})
// Used without optional onClick
<UserAvatar />---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still extract default non-primitive parameters to constants. The compiler cannot automatically stabilize default parameter values to preserve memo() optimization.
See react-compiler-guide.md for more details.
1.2 Extract to Memoized Components
Extract expensive work into memoized components to enable early returns before computation.
❌ Incorrect: computes avatar even when loading
function Profile({ user, loading }: Props) {
const avatar = useMemo(() => {
const id = computeAvatarId(user);
return <Avatar id={id} />;
}, [user]);
if (loading) {
return <Skeleton />;
}
return <div>{avatar}</div>;
}✅ Correct: skips computation when loading
const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
const id = useMemo(() => computeAvatarId(user), [user]);
return <Avatar id={id} />;
})
function Profile({ user, loading }: Props) {
if (loading) {
return <Skeleton />;
}
return (
<div>
<UserAvatar user={user} />
</div>
)
}---
React Compiler Note
✅ Handled automatically - If your project has React Compiler enabled, manual memoization with memo() and useMemo() is unnecessary. The compiler automatically optimizes re-renders.
See react-compiler-guide.md for more details.
1.5 Use Functional setState Updates
When updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.
❌ Incorrect: requires state as dependency
function TodoList() {
const [items, setItems] = useState(initialItems)
// Callback must depend on items, recreated on every items change
const addItems = useCallback((newItems: Item[]) => {
setItems([...items, ...newItems])
}, [items]) // ❌ items dependency causes recreations
// Risk of stale closure if dependency is forgotten
const removeItem = useCallback((id: string) => {
setItems(items.filter(item => item.id !== id))
}, []) // ❌ Missing items dependency - will use stale items!
return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
}The first callback is recreated every time items changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial items value.
✅ Correct: stable callbacks, no stale closures
function TodoList() {
const [items, setItems] = useState(initialItems)
// Stable callback, never recreated
const addItems = useCallback((newItems: Item[]) => {
setItems(curr => [...curr, ...newItems])
}, []) // ✅ No dependencies needed
// Always uses latest state, no stale closure risk
const removeItem = useCallback((id: string) => {
setItems(curr => curr.filter(item => item.id !== id))
}, []) // ✅ Safe and stable
return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
}---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still use functional setState updates. The compiler cannot infer when to use functional updates, and this pattern is essential for correctness and preventing stale closure bugs.
See react-compiler-guide.md for more details.
2.7 Hoist RegExp Creation
Don't create RegExp inside render. Hoist to module scope or memoize with useMemo().
❌ Incorrect: RegExp created every render
function Highlighter({ text, query }: Props) {
const regex = new RegExp(`(${query})`, 'gi');
const parts = text.split(regex);
return <>{parts.map((part, i) => ...)}</>;
}✅ Correct: memoized or hoisted
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
function Highlighter({ text, query }: Props) {
const regex = useMemo(
() => new RegExp(`(${escapeRegex(query)})`, 'gi'),
[query]
);
const parts = text.split(regex);
return <>{parts.map((part, i) => ...)}</>;
}Warning: global regex (/g) has mutable lastIndex state:
const regex = /foo/g
regex.test('foo') // true, lastIndex = 3
regex.test('foo') // false, lastIndex = 0---
React Compiler Note
✅ Handled automatically - If your project has React Compiler enabled, the compiler automatically hoists RegExp creation and memoizes values. Manual hoisting/memoization is unnecessary.
See react-compiler-guide.md for more details.
2.3 Hoist Static JSX Elements
Extract static JSX outside components to avoid re-creation.
❌ Incorrect: recreates element every render
function LoadingSkeleton() {
return <div className="animate-pulse h-20 bg-gray-200" />
}
function Container() {
return (
<div>
{loading && <LoadingSkeleton />}
</div>
)
}✅ Correct: reuses same element
const loadingSkeleton = (
<div className="animate-pulse h-20 bg-gray-200" />
)
function Container() {
return (
<div>
{loading && loadingSkeleton}
</div>
)
}This is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render.
---
React Compiler Note
✅ Handled automatically - If your project has React Compiler enabled, the compiler automatically hoists static JSX elements. Manual hoisting is unnecessary.
See react-compiler-guide.md for more details.
3.4 Initialize App Once, Not Per Mount
Do not put app-wide initialization that must run once per app load inside useEffect() of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.
❌ Incorrect: runs twice in dev, re-runs on remount
function Comp() {
useEffect(() => {
loadFromStorage()
checkAuthToken()
}, [])
// ...
}✅ Correct: once per app load
let didInit = false
function Comp() {
useEffect(() => {
if (didInit) {
return;
}
didInit = true
loadFromStorage()
checkAuthToken()
}, [])
// ...
}Reference: https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application
---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still use module-level guards for app initialization. The compiler cannot infer that initialization should run once per app load rather than per component mount.
See react-compiler-guide.md for more details.
1.11 Put Interaction Logic in Event Handlers
If a side effect is triggered by a specific user action (submit, click, drag), run it in that event handler. Do not model the action as state + effect; it makes effects re-run on unrelated changes and can duplicate the action.
❌ Incorrect: event modeled as state + effect
function Form() {
const [submitted, setSubmitted] = useState(false)
const theme = useContext(ThemeContext)
useEffect(() => {
if (submitted) {
post('/api/register')
showToast('Registered', theme)
}
}, [submitted, theme])
return <button onClick={() => setSubmitted(true)}>Submit</button>
}✅ Correct: do it in the handler
function Form() {
const theme = useContext(ThemeContext)
function handleSubmit() {
post('/api/register')
showToast('Registered', theme)
}
return <button onClick={handleSubmit}>Submit</button>
}---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still put interaction logic in event handlers. The compiler cannot infer that side effects should be triggered by user actions rather than state changes.
See react-compiler-guide.md for more details.
Reference: https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler
1.6 Use Lazy State Initialization
Pass a function to useState for expensive initial values. Without the function form, the initializer runs on every render even though the value is only used once.
❌ Incorrect: runs on every render
function FilteredList({ items }: { items: Item[] }) {
// buildSearchIndex() runs on EVERY render, even after initialization
const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))
const [query, setQuery] = useState('')
// When query changes, buildSearchIndex runs again unnecessarily
return <SearchResults index={searchIndex} query={query} />
}
function UserProfile() {
// JSON.parse runs on every render
const [settings, setSettings] = useState(
JSON.parse(localStorage.getItem('settings') || '{}')
)
return <SettingsForm settings={settings} onChange={setSettings} />
}✅ Correct: runs only once
function FilteredList({ items }: { items: Item[] }) {
// buildSearchIndex() runs ONLY on initial render
const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))
const [query, setQuery] = useState('')
return <SearchResults index={searchIndex} query={query} />
}
function UserProfile() {
// JSON.parse runs only on initial render
const [settings, setSettings] = useState(() => {
const stored = localStorage.getItem('settings')
return stored ? JSON.parse(stored) : {}
})
return <SettingsForm settings={settings} onChange={setSettings} />
}Use lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations.
For simple primitives (useState(0)), direct references (useState(props.value)), or cheap literals (useState({})), the function form is unnecessary.
---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still use lazy state initialization. The compiler cannot transform direct value initialization into the function form automatically.
See react-compiler-guide.md for more details.
4.1 Named Imports
Always used named imports from the react library.
❌ Incorrect: default import
import React from 'react';✅ Correct: named imports
import { useEffect, useState } from 'react';❌ Incorrect: wildcard import
import * as React from 'react';✅ Correct: named imports
import { useEffect, useState } from 'react';---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still use named imports. This is an import syntax requirement, not a runtime optimization.
See react-compiler-guide.md for more details.
1.3 Narrow Effect Dependencies
Specify primitive dependencies instead of objects to minimize effect re-runs.
❌ Incorrect: re-runs on any user field change
useEffect(() => {
console.log(user.id);
}, [user])✅ Correct: re-runs only when id changes
useEffect(() => {
console.log(user.id);
}, [user.id])For derived state, compute outside effect:
❌ Incorrect: runs on width=767, 766, 765...
useEffect(() => {
if (width < 768) {
enableMobileMode();
}
}, [width])✅ Correct: runs only on boolean transition
const isMobile = width < 768
useEffect(() => {
if (isMobile) {
enableMobileMode();
}
}, [isMobile])---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still narrow effect dependencies. The compiler cannot restructure your code to use primitive dependencies instead of objects.
See react-compiler-guide.md for more details.
4.2 No forwardRef
forwardRef was deprecated in React 19.
❌ Incorrect: `forwardRef` used
const Input = forwardRef((props, ref) => <input ref={ref} {...props} />);✅ Correct: ref as a prop
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />;
}---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must migrate from forwardRef to ref props. This is a React 19 API migration requirement, not a compiler optimization.
See react-compiler-guide.md for more details.
Don't Define Components Inside Components
Defining a component inside another component creates a new component type on every render. React sees a different component each time and fully remounts it, destroying all state and DOM.
A common reason developers do this is to access parent variables without passing props. Always pass props instead.
❌ Incorrect: remounts on every render
function UserProfile({ user, theme }) {
// Defined inside to access `theme` - BAD
const Avatar = () => (
<img
src={user.avatarUrl}
className={theme === 'dark' ? 'avatar-dark' : 'avatar-light'}
/>
)
// Defined inside to access `user` - BAD
const Stats = () => (
<div>
<span>{user.followers} followers</span>
<span>{user.posts} posts</span>
</div>
)
return (
<div>
<Avatar />
<Stats />
</div>
)
}Every time UserProfile renders, Avatar and Stats are new component types. React unmounts the old instances and mounts new ones, losing any internal state, running effects again, and recreating DOM nodes.
✅ Correct: pass props instead
function Avatar({ src, theme }: { src: string; theme: string }) {
return (
<img
src={src}
className={theme === 'dark' ? 'avatar-dark' : 'avatar-light'}
/>
)
}
function Stats({ followers, posts }: { followers: number; posts: number }) {
return (
<div>
<span>{followers} followers</span>
<span>{posts} posts</span>
</div>
)
}
function UserProfile({ user, theme }) {
return (
<div>
<Avatar src={user.avatarUrl} theme={theme} />
<Stats followers={user.followers} posts={user.posts} />
</div>
)
}Symptoms of this bug:
- Input fields lose focus on every keystroke
- Animations restart unexpectedly
useEffectcleanup/setup runs on every parent render- Scroll position resets inside the component
2.4 Optimize SVG Precision
Reduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered.
❌ Incorrect: excessive precision
<path d="M 10.293847 20.847362 L 30.938472 40.192837" />✅ Correct: 1 decimal place
<path d="M 10.3 20.8 L 30.9 40.2" />This optimization can be automated with SVGO
npx svgo --precision=1 --multipass icon.svg---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still optimize SVG precision. This is a build-time asset optimization, not a React code optimization.
See react-compiler-guide.md for more details.
2.5 Prevent Hydration Mismatch Without Flickering
When rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous script that updates the DOM before React hydrates.
❌ Incorrect: breaks SSR
function ThemeWrapper({ children }: { children: ReactNode }) {
// localStorage is not available on server - throws error
const theme = localStorage.getItem('theme') || 'light'
return (
<div className={theme}>
{children}
</div>
)
}✅ Correct: no flicker, no hydration mismatch
function ThemeWrapper({ children }: { children: ReactNode }) {
return (
<>
<div id="theme-wrapper">
{children}
</div>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
try {
var theme = localStorage.getItem('theme') || 'light';
var el = document.getElementById('theme-wrapper');
if (el) el.className = theme;
} catch (e) {}
})();
`,
}}
/>
</>
)
}❌ Incorrect: visual flickering
function ThemeWrapper({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState('light')
useEffect(() => {
// Runs after hydration - causes visible flash
const stored = localStorage.getItem('theme')
if (stored) {
setTheme(stored)
}
}, [])
return (
<div className={theme}>
{children}
</div>
)
}✅ Correct: no flicker, no hydration mismatch
function ThemeWrapper({ children }: { children: ReactNode }) {
return (
<>
<div id="theme-wrapper">
{children}
</div>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
try {
var theme = localStorage.getItem('theme') || 'light';
var el = document.getElementById('theme-wrapper');
if (el) el.className = theme;
} catch (e) {}
})();
`,
}}
/>
</>
)
}The inline script executes synchronously before showing the element, ensuring the DOM already has the correct value. No flickering, no hydration mismatch.
This pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values.
---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still handle SSR hydration mismatches explicitly. The compiler cannot infer client-side only state handling.
See react-compiler-guide.md for more details.
Quick Reference Checklists
Use these checklists to quickly audit React code for common patterns and optimizations. Each checklist links to the relevant detailed reference file.
---
New Component Checklist
Use when creating a new React component from scratch:
- [ ] Using named imports from 'react'? → 4.1 Named Imports
- [ ] Using
refprop instead of forwardRef? → 4.2 No forwardRef - [ ] Static JSX hoisted to module scope? → 2.3 Hoist Static JSX
- [ ] RegExp created at module scope or memoized? → 2.7 Hoist RegExp
- [ ] Expensive initialization using lazy pattern? → 1.6 Lazy State Initialization
- [ ] App initialization using module guards? → 3.4 Initialize App Once
- [ ] State updates using functional form when needed? → 1.5 Functional setState
- [ ] Deriving state during render instead of effects? → 1.8 Calculate Derived State
- [ ] Default non-primitive params extracted to constants? → 1.10 Extract Default Parameter
- [ ] Avoiding useMemo for simple expressions? → 1.9 Avoid useMemo Simple Expressions
- [ ] Components defined at module scope, not inside other components? → 1.13 No Inline Components
---
Performance Review Checklist
Use when reviewing React code for performance issues:
- [ ] Components re-rendering unnecessarily? → 1.2 Extract Memoized Components
- [ ] Subscribing to state that's only read in callbacks? → 1.1 Defer State Reads
- [ ] Effect dependencies include objects instead of primitives? → 1.3 Narrow Effect Dependencies
- [ ] Subscribing to continuous values (width) instead of derived state (isMobile)? → 1.4 Subscribe Derived State
- [ ] Deriving state with effects instead of render? → 1.8 Calculate Derived State
- [ ] Interaction logic in effects instead of handlers? → 1.11 Interaction Logic in Handlers
- [ ] Using state for frequently-changing transient values? → 1.12 useRef for Transient
- [ ] Manual loading states instead of useTransition? → 2.8 useTransition Over Manual Loading
- [ ] Effects re-subscribing on every render? → 3.1 Store Event Handlers
- [ ] Expensive functions called repeatedly with same inputs? → 3.3 Cache Repeated Calls
- [ ] Long lists causing slow renders? → 2.2 CSS content-visibility
- [ ] SVG animations janky? → 2.1 Animate SVG Wrapper
- [ ] Typing/scrolling feels sluggish? → 1.7 Transitions
- [ ] Components defined inside other components? → 1.13 No Inline Components
---
SSR/SSG Checklist
Use when working with server-side rendering or static site generation:
- [ ] Hydration mismatch errors? → 2.5 Prevent Hydration Mismatch
- [ ] Client-only state (localStorage, cookies) causing issues? → 2.5 Prevent Hydration Mismatch
- [ ] Theme/preferences flickering on load? → 2.5 Prevent Hydration Mismatch
---
Effect Dependencies Checklist
Use when debugging useEffect issues:
- [ ] Effect running infinitely? → 1.3 Narrow Dependencies or 1.5 Functional setState
- [ ] Effect running too frequently? → 1.3 Narrow Dependencies or 3.1 Store Event Handlers
- [ ] Effect synchronizing derived state? → 1.8 Calculate Derived State
- [ ] Effect triggered by user interaction? → 1.11 Interaction Logic in Handlers
- [ ] Callback has stale values? → 1.5 Functional setState or 3.2 useLatest / useEffectEvent
- [ ] Effect re-subscribing on callback changes? → 3.1 Store Event Handlers
---
React 19 Migration Checklist
Use when migrating to React 19:
- [ ] Remove default React import → 4.1 Named Imports
- [ ] Replace forwardRef with ref prop → 4.2 No forwardRef
- [ ] Use
useEffectEventfor stable event handlers (19.2+) → 3.1 Store Event Handlers - [ ] Use
<Activity>for show/hide state preservation → 2.6 Activity Component - [ ] Check if React Compiler is enabled → React Compiler Guide
---
Bundle Size Optimization Checklist
Use when optimizing bundle size:
- [ ] SVGs optimized with SVGO (precision=1)? → 2.4 Optimize SVG Precision
- [ ] Static JSX hoisted to module scope? → 2.3 Hoist Static JSX
- [ ] Named imports used instead of default? → 4.1 Named Imports
---
Re-render Debugging Checklist
Use when a component is re-rendering too frequently:
Step 1: Identify the cause
- Check React DevTools Profiler for render frequency
- Add console.log to identify what's triggering renders
Step 2: Check these common issues
- [ ] Parent re-renders causing child re-renders? → 1.2 Extract Memoized Components
- [ ] Subscribing to changing state unnecessarily? → 1.1 Defer State Reads or 1.4 Subscribe Derived State
- [ ] Object/array dependencies in effects? → 1.3 Narrow Effect Dependencies
- [ ] Derived state causing extra renders? → 1.8 Calculate Derived State
- [ ] Callbacks recreated on every render? → 1.5 Functional setState
- [ ] Frequently-changing values causing re-renders? → 1.12 useRef for Transient
- [ ] Non-urgent updates blocking UI? → 1.7 Transitions
- [ ] Input fields losing focus on keystroke? → 1.13 No Inline Components
- [ ] Animations restarting unexpectedly? → 1.13 No Inline Components
---
Advanced Patterns Checklist
Use when implementing advanced optimization patterns:
- [ ] Need stable callbacks without adding dependencies? → 3.2 useLatest or useEffectEvent (React 19.2+)
- [ ] React 19.2+ event handlers in effects? → 3.1 Store Event Handlers
- [ ] Expensive computations called multiple times? → 3.3 Cache Repeated Calls
- [ ] App initialization running on every mount? → 3.4 Initialize App Once
- [ ] Components toggling visibility losing state? → 2.6 Activity Component
---
React Compiler Checklist
Use when working with React Compiler enabled projects:
- [ ] Confirmed React Compiler is enabled? → React Compiler Guide
- [ ] Removed manual memo() wrapping? (compiler handles it)
- [ ] Removed unnecessary useMemo() for simple values? (compiler handles it)
- [ ] Removed unnecessary useCallback()? (compiler handles it)
- [ ] Still applying state management patterns? → Section 1
- [ ] Still applying effect patterns? → Section 1.3, 3.1
- [ ] Still applying CSS optimizations? → Section 2
---
Code Review Quick Audit
Use when doing a quick code review of React components:
High Priority (Fix These):
- [ ] forwardRef usage → 4.2 No forwardRef
- [ ] Default React import → 4.1 Named Imports
- [ ] Hydration mismatches → 2.5 Prevent Hydration Mismatch
- [ ] Infinite re-render loops → 1.3, 1.5
- [ ] Stale closures in callbacks → 1.5 Functional setState
- [ ] Components defined inside other components → 1.13 No Inline Components
Medium Priority (Optimize If Time Permits):
- [ ] Unnecessary subscriptions → 1.1 Defer State Reads
- [ ] Object dependencies in effects → 1.3 Narrow Dependencies
- [ ] Continuous value subscriptions → 1.4 Subscribe Derived State
- [ ] Non-lazy expensive initialization → 1.6 Lazy Initialization
Low Priority (Nice to Have):
- [ ] Static JSX not hoisted → 2.3 Hoist Static JSX
- [ ] RegExp created in render → 2.7 Hoist RegExp
- [ ] SVG precision not optimized → 2.4 Optimize SVG
React Compiler Guide
What is React Compiler?
React Compiler is an automatic optimization tool that transforms React code at build time to improve performance. It automatically memoizes components and values, reducing the need for manual optimization with memo(), useMemo(), and useCallback().
Learn more: React Compiler Documentation
---
How to Check if Your Project Uses React Compiler
1. Check package.json for babel-plugin-react-compiler or similar 2. Check build config (babel.config.js, vite.config.js, next.config.js) for compiler plugin 3. Look for compiler output in build logs 4. If unsure, ask the user: "Does this project use React Compiler?"
---
What React Compiler Handles Automatically
✅ Automatic Optimizations (No Manual Work Needed)
When React Compiler is enabled, these patterns are automatically optimized - you don't need to manually apply them:
Memoization:
- 1.2 Extract to Memoized Components - Compiler auto-memoizes components
- Components wrapped with
memo()- Compiler does this automatically - Values wrapped with
useMemo()- Compiler memoizes automatically - Callbacks wrapped with
useCallback()- Compiler stabilizes automatically
Static Extraction:
- 2.3 Hoist Static JSX Elements - Compiler hoists automatically
- 2.7 Hoist RegExp Creation - Compiler hoists automatically
Effect Dependencies:
- Stable callback references in dependency arrays - Compiler handles this
---
What React Compiler Does NOT Handle
❌ Manual Optimizations Still Required
Even with React Compiler enabled, you still need to manually apply these patterns:
State Management Patterns:
- 1.1 Defer State Reads - Compiler can't know you don't need subscription
- 1.4 Subscribe to Derived State - Requires semantic understanding of intent
- 1.5 Functional setState Updates - Compiler can't infer functional updates
- 1.6 Lazy State Initialization - Requires function wrapper syntax
- 1.14 Split Combined Hook Computations - Requires manual code restructuring
- 1.15 Use useDeferredValue - Requires explicit API usage
Effect Optimizations:
- 1.3 Narrow Effect Dependencies - Requires code restructuring
- 3.1 Store Event Handlers in Refs - Requires useEffectEvent pattern
Rendering Performance:
- 1.7 Transitions for Non-Urgent Updates - Requires explicit
startTransition() - 2.1 Animate SVG Wrapper - Requires DOM structure change
- 2.2 CSS content-visibility - CSS optimization, not React code
- 2.4 Optimize SVG Precision - Build-time SVG optimization
- 2.5 Prevent Hydration Mismatch - Requires explicit SSR handling
- 2.6 Activity Component - Requires React 19
<Activity>component - 2.8 Avoid useMemo For Simple Expressions - Code simplification preference
Advanced Patterns:
- 3.2 useLatest for Stable Callbacks - Custom hook pattern
- 3.3 Cache Repeated Function Calls - Module-level caching
React 19 Migration:
- 4.1 Named Imports - Import syntax requirement
- 4.2 No forwardRef - API migration requirement
---
Decision Guide: Should I Optimize Manually?
Step 1: Check for React Compiler
# Check package.json
grep -i "react-compiler" package.json
# Check babel config
cat babel.config.js | grep -i compiler
# Check Next.js config
cat next.config.js | grep -i compilerStep 2: Apply the Right Strategy
If React Compiler is ENABLED:
- Skip manual
memo(),useMemo(),useCallback()- compiler handles it - Skip hoisting static JSX/RegExp - compiler handles it
- Still apply all other optimizations from this guide
If React Compiler is NOT enabled:
- Apply all optimizations from this guide as needed
- Manual memoization is necessary and beneficial
Step 3: When in Doubt
If you're unsure whether a project uses React Compiler: 1. Ask the user 2. Check the build configuration files 3. Assume it's NOT enabled and apply manual optimizations (safer default)
---
Migration Path
If Adding React Compiler to Existing Project
1. Before enabling compiler:
- Ensure code follows React rules (no conditional hooks, etc.)
- Remove ESLint disables for react-hooks rules
2. After enabling compiler:
- Remove manual
memo()wrapping (compiler does this) - Remove unnecessary
useMemo()for simple values - Remove unnecessary
useCallback()for event handlers - Keep all other optimizations (state management, effects, CSS, etc.)
3. Keep these patterns:
- Functional setState updates
- Lazy state initialization
- Narrow effect dependencies
- Transitions for non-urgent updates
- All CSS/rendering optimizations
- All React 19 migration patterns
---
Quick Reference
| Pattern | Auto with Compiler? | Manual Still Needed? |
|---|---|---|
| memo() components | ✅ Yes | ❌ No |
| useMemo() values | ✅ Yes | ❌ No |
| useCallback() handlers | ✅ Yes | ❌ No |
| Hoist static JSX | ✅ Yes | ❌ No |
| Hoist RegExp | ✅ Yes | ❌ No |
| Defer state reads | ❌ No | ✅ Yes |
| Functional setState | ❌ No | ✅ Yes |
| Lazy initialization | ❌ No | ✅ Yes |
| Narrow dependencies | ❌ No | ✅ Yes |
| Split combined hooks | ❌ No | ✅ Yes |
| useDeferredValue | ❌ No | ✅ Yes |
| Transitions | ❌ No | ✅ Yes |
| CSS optimizations | ❌ No | ✅ Yes |
| SSR/Hydration | ❌ No | ✅ Yes |
| Advanced patterns | ❌ No | ✅ Yes |
---
Common Mistakes
❌ Over-optimizing with Compiler Enabled
// DON'T: Manual memo when compiler is enabled
const MemoizedComponent = memo(function MyComponent() {
// ... compiler already memoizes this
})
// DON'T: Manual useMemo for simple values
const doubled = useMemo(() => count * 2, [count])
// Compiler handles this automatically✅ Right Approach with Compiler
// DO: Write clean code, let compiler optimize
function MyComponent({ count }) {
const doubled = count * 2
return <div>{doubled}</div>
}
// DO: Still use functional updates
setCount(curr => curr + 1)
// DO: Still use transitions
startTransition(() => {
setSearchResults(newResults)
})---
When to Reference This Guide
Load this reference when:
- Determining which optimizations to apply
- Project has React Compiler and you're unsure what's still needed
- Migrating to/from React Compiler
- User asks "Do I need to memoize this with React Compiler?"
1.4 Subscribe to Derived State
Subscribe to derived boolean state instead of continuous values to reduce re-render frequency.
❌ Incorrect: re-renders on every pixel change
function Sidebar() {
const width = useWindowWidth(); // updates continuously
const isMobile = width < 768;
return <nav className={isMobile ? 'mobile' : 'desktop'}>
}✅ Correct: re-renders only when boolean changes
function Sidebar() {
const isMobile = useMediaQuery('(max-width: 767px)')
return <nav className={isMobile ? 'mobile' : 'desktop'}>
}---
React Compiler Note
❌ Manual optimization required - Even with React Compiler enabled, you must still subscribe to derived state instead of continuous values. The compiler cannot infer that you should subscribe to a boolean instead of the raw value.
See react-compiler-guide.md for more details.
Related skills
How it compares
Pick accelint-react-best-practices over generic React lint rules when you need hook-level root-cause patterns, hydration fixes, and React 19-specific guidance with audit templates.
FAQ
What React versions does accelint-react-best-practices cover?
accelint-react-best-practices covers React 19 and newer APIs including useEffectEvent, the Activity component, and ref as a regular prop. Version 1.8.0 explicitly deprecates forwardRef patterns replaced in React 19+.
Does accelint-react-best-practices always recommend useMemo and useCallback?
accelint-react-best-practices checks for babel-plugin-react-compiler or react-compiler-webpack-plugin first. When React Compiler is enabled, the skill skips manual memoization guidance and still applies state, effect, and CSS performance rules.
How is accelint-react-best-practices structured for agents?
accelint-react-best-practices uses progressive disclosure: start with AGENTS.md, then load specific reference markdown files only for the identified issue. About 30 reference files cover re-renders, hydration, effects, and React 19 patterns.