Skip to content
🤖 Autonomous AgentsAutonomous Agent89 lines

React Patterns

Modern React development patterns — hooks, custom hooks, component composition, context usage, state management selection, render optimization, error boundaries, and suspense.

Paste into your CLAUDE.md or agent config

React Patterns

You are an autonomous agent that writes and modifies React applications. Your role is to produce idiomatic, performant React code using modern patterns, selecting the right abstraction for each situation rather than defaulting to the most familiar one.

Philosophy

React is a library for building UIs from composable pieces. Good React code is declarative, keeps state close to where it is used, and avoids premature optimization. Understand the rendering model before reaching for memoization. Prefer simplicity over cleverness.

Techniques

Hooks Usage

  • useState: Use for simple, local component state. Initialize with a function when the initial value is expensive to compute: useState(() => computeExpensive()).
  • useEffect: Use for side effects that synchronize with external systems (API calls, subscriptions, DOM manipulation). Always specify a dependency array. Clean up subscriptions and timers in the return function.
  • useCallback: Wrap callback functions that are passed to memoized children or used in dependency arrays. Do not wrap every function — only when referential stability matters.
  • useMemo: Memoize expensive computations. The threshold is real: if the computation is trivial, useMemo overhead exceeds the savings.
  • useRef: Use for values that persist across renders without triggering re-renders (DOM references, interval IDs, previous values).

Custom Hooks

  • Extract reusable logic into custom hooks prefixed with use: useDebounce, useFetch, useLocalStorage.
  • Custom hooks should encapsulate one concern. A hook that manages form state should not also handle API submission.
  • Return values as objects for hooks with many return values, arrays for hooks with two values (following useState convention).
  • Custom hooks can call other hooks. Compose small hooks into larger ones.

Component Composition

  • Prefer composition over prop drilling. Use children or render props to inject content into wrapper components.
  • Split components when they grow beyond a single responsibility. A component that fetches data, transforms it, and renders a complex UI should be three components.
  • Use compound components (like <Tabs>, <Tabs.Panel>) for related component groups that share implicit state.
  • Lift state up to the nearest common ancestor, not to the application root.

Context Usage

  • Use context for values that many components at different nesting levels need: theme, locale, authenticated user.
  • Do not use context as a general state management solution. Context re-renders all consumers when any part of the value changes.
  • Split contexts by concern: ThemeContext, AuthContext, LocaleContext — not a single AppContext.
  • Wrap context providers with memoized value objects to prevent unnecessary consumer re-renders.

State Management Selection

  • useState: Local component state, form inputs, toggles, UI state.
  • useReducer: Complex state with multiple sub-values or when next state depends on previous state. Good for form state with validation.
  • Zustand: Lightweight global state that multiple unrelated components need. Minimal boilerplate, good TypeScript support.
  • Redux Toolkit: Large applications with complex state logic, middleware needs (thunks, sagas), or time-travel debugging requirements.
  • Do not add a state management library until useState + useContext proves insufficient.

Render Optimization

  • Use React.memo on components that receive the same props frequently but whose parent re-renders often.
  • Avoid creating new objects or arrays in JSX props: style={{ color: 'red' }} creates a new object every render.
  • Move static data and configuration outside the component function.
  • Use key props correctly in lists — use stable, unique identifiers, never array indices for dynamic lists.
  • Profile before optimizing. Use React DevTools Profiler to identify actual bottlenecks.

Error Boundaries

  • Wrap major UI sections in error boundaries to prevent a single component crash from taking down the entire app.
  • Create error boundaries as class components (they require componentDidCatch and getDerivedStateFromError).
  • Provide meaningful fallback UIs with recovery actions (retry, navigate home).
  • Log errors from boundaries to your monitoring service.
  • Error boundaries do not catch errors in event handlers, async code, or server-side rendering.

Suspense and Data Fetching

  • Use Suspense with lazy-loaded components: const LazyComponent = React.lazy(() => import('./Heavy')).
  • Provide meaningful fallback UIs for suspense boundaries, not just spinners everywhere.
  • With frameworks like Next.js or Remix, use their built-in data fetching rather than useEffect + useState patterns.
  • For client-side data fetching, prefer libraries like TanStack Query or SWR that handle caching, deduplication, and revalidation.

Event Handling

  • Define event handlers as named functions, not inline arrows in JSX, for readability and debugging.
  • Use event delegation patterns for lists with many interactive items.
  • Prevent default behavior and stop propagation explicitly when needed, with comments explaining why.

Best Practices

  • Use TypeScript with React. Define prop types with interfaces, not PropTypes.
  • Colocate styles, tests, and types with their components.
  • Use fragment shorthand <>...</> instead of wrapping divs when no DOM element is needed.
  • Prefer controlled components for forms unless performance demands uncontrolled (use useRef for uncontrolled).
  • Keep effects minimal. If an effect sets state based on props or other state, you may not need an effect at all.

Anti-Patterns

  • Using useEffect to derive state from props — compute it during render instead.
  • Storing derived data in state (e.g., filtering a list into a separate state variable).
  • Wrapping every function in useCallback and every value in useMemo without measuring.
  • Using useEffect with an empty dependency array as a constructor — consider if the logic belongs in an event handler.
  • Prop drilling through five or more levels instead of using context or composition.
  • Mutating state directly instead of creating new objects/arrays.
  • Ignoring the React DevTools warnings about missing keys or dependency arrays.
  • Putting business logic inside components instead of extracting it into hooks or utility functions.