AgentSkillsCN

react-best-practices

2025–26年现代React最佳实践与反模式目录。在编写、审查或重构React组件、钩子与状态管理时使用此功能。涵盖组件设计、状态模式、钩子滥用、性能优化、数据获取,以及代码组织。

SKILL.md
--- frontmatter
name: react-best-practices
description: "Modern React best practices and anti-pattern catalog (2025-26). Use when writing, reviewing, or refactoring React components, hooks, and state management. Covers component design, state patterns, hooks misuse, performance, data fetching, and code organization."

React Best Practices & Anti-Patterns

Modern React conventions (2025-26). Covers what to do and what to avoid. For code examples, see examples.md.

Severity Levels

Each rule is tagged with a severity for use by consuming agents:

  • CRITICAL — Will cause bugs, broken reconciliation, or maintenance nightmares
  • HIGH — Will cause performance issues or scaling problems
  • MEDIUM — Will hurt maintainability or developer experience

Component Design (CRITICAL)

  • Components must be pure — same inputs = same outputs, no side effects during render
  • Business logic in hooks/helpers, NOT in component bodies
  • Container components fetch data; presentational components receive props and render UI
  • Helper functions extracted OUTSIDE the component body
  • Max 200 lines per component — split if larger
  • Max 5-7 props — more suggests the component does too much
  • One component per file (small colocated internal helpers are fine)

Composition Patterns

  • Compose small focused components over monolithic ones
  • "Lift Content Up" — move children to the parent when the wrapper doesn't use them for logic
  • "Push State Down" — keep state in the component that actually needs it
  • Prefer children prop and composition over deep prop drilling

Derive, Don't Store (CRITICAL)

The #1 React anti-pattern. Look for it in every review.

  • NEVER store derived values in useState — compute during render
  • NEVER use useState + useEffect to sync a computed value — just compute it
  • Use useMemo ONLY if the computation is expensive (measured, not assumed)
  • If a value can be calculated from existing props/state, calculate it inline

State Management (HIGH)

State Location

  • Colocate state with the components that use it
  • Don't lift state higher than necessary — it causes unnecessary re-renders
  • Don't duplicate state across components

Context API

  • Context is for dependency injection (auth, theme), NOT global state management
  • Context changes re-render ALL consumers — split contexts by concern
  • Prefer hook return values over Context when only one subtree needs the data

State Hygiene

  • Not everything needs to be in useState — only values that change over time and affect the UI
  • Combine related state with useReducer instead of multiple useState calls
  • URL-dependent state (filters, pagination, search) belongs in URL search params, not component state

Hooks (HIGH)

useEffect Rules

Before each useEffect, ask: Is there an external system being synchronized? If no, it's misused.

  • NEVER use useEffect for derived state — compute during render
  • NEVER use useEffect for event handling — put logic in the event handler
  • NEVER chain useEffects that trigger each other — usually means derived state
  • ALWAYS declare all dependencies correctly
  • ALWAYS clean up subscriptions, timers, and event listeners

Memoization Rules

Most useMemo/useCallback calls are unnecessary. Be skeptical:

  • useMemo — only for actually expensive computations (sorting large datasets, complex transforms)
  • useCallback — only when the function is passed to a React.memo child
  • Simple string concatenation, arithmetic, or boolean checks do NOT need memoization

Render Factories (CRITICAL)

camelCase functions returning JSX are NOT React components. They break reconciliation, hooks, and dev tools.

  • NEVER use renderThing() pattern — use <Thing /> component syntax
  • ALWAYS use PascalCase for anything that returns JSX
  • Render factories lose component identity on every render, causing full unmount/remount

Inline Creation in JSX (HIGH)

New arrays, objects, and functions created inline in JSX props break React.memo on children.

  • Extract static arrays/objects to module-level constants
  • Extract dynamic arrays/objects to useMemo
  • Extract inline functions to useCallback (only when passed to memoized children)

Over-Engineering (CRITICAL)

Premature Abstraction

  • Abstractions with only one consumer are premature — inline it
  • "Reusable" hooks with hardcoded field names are not reusable — accept config as parameters
  • Context storing state that could be computed locally is over-engineered

Wrapper Components

  • Components that only call a hook and return null are unnecessary — call the hook directly
  • Wrappers that only pass props through add indirection without value

Data Fetching (HIGH)

  • ALL data fetching in custom hooks, never in component bodies
  • Use the project's useApiQuery/useApiMutation core hooks
  • Handle loading, error, and empty states in the container component
  • Use try-catch in async functions within hooks

Tailwind CSS (MEDIUM)

  • Use utility classes for all styling — no inline style={} objects
  • Use responsive prefixes (sm:, md:, lg:) for responsive design
  • Extract repeated class combinations into reusable components (Button, Card, Badge)
  • Prefer the project's components/ui/ over recreating common elements

Error Boundaries (HIGH)

  • Use react-error-boundary package for function component-friendly API
  • Include resetKeys={[location.pathname]} so boundaries reset on navigation
  • Provide a "Try again" button that calls resetErrorBoundary in fallback UI
  • Error boundaries do NOT catch errors in event handlers, async code, or SSR — use try/catch there

Key Prop Patterns (CRITICAL)

  • NEVER use array index as key when lists can be reordered, filtered, or modified
  • NEVER use Math.random() or other unstable values as keys — causes full unmount/remount
  • When mapping Fragments, put key on <React.Fragment key={id}>, not on a child

Conditional Rendering (HIGH)

  • NEVER use {count && <Component />} when count can be 0 — renders literal 0
  • Use {count > 0 && <Component />} or ternary instead
  • Replace nested ternaries with early returns or extracted components
  • For multiple UI states (loading/error/empty/success), use early returns pattern

Accessibility (HIGH)

  • Add aria-label to icon-only buttons — without it, they're invisible to screen readers
  • Link error messages to fields with aria-describedby and aria-invalid
  • Use aria-live="polite" for dynamic content updates (search results, notifications, toasts)
  • Trap focus inside modals; provide escape path (Escape key + visible Close button)
  • Announce route changes for screen readers (SPA navigation is silent by default)

Performance Beyond Memoization (MEDIUM)

  • Use React.lazy() + <Suspense> for route-level code splitting
  • Use Vite manualChunks to split vendor bundles for better caching
  • Use top-level static paths in lazy(() => import('./X')) — dynamic paths break build analysis

Axios + React Patterns (HIGH)

  • Cancel in-flight requests in useEffect cleanup using AbortController
  • Use centralized Axios instance with baseURL, default headers, and interceptors
  • Use request interceptors for auth tokens, response interceptors for 401/403 handling

React 19 Patterns (MEDIUM)

  • Accept ref as a regular prop instead of using forwardRef (React 19+)
  • With React Compiler enabled, avoid adding memo/useMemo/useCallback unless measured

Code Organization (MEDIUM)

Feature-Based Structure

  • Colocate component + hook + helpers + tests per feature
  • Shared utilities go in utils/ or components/ui/

File Quality

  • Order: imports, constants, helpers, component, exports
  • Reuse existing types and constants over creating new ones