React Operations
Comprehensive React skill covering hooks, component architecture, state management, Server Components, and performance optimization.
React 19 ecosystem facts verified as of 2026-07.
Hook Selection Decision Tree
What problem are you solving?
│
├─ Storing UI state that triggers re-renders
│ ├─ Simple value (string, number, boolean)
│ │ └─ useState
│ ├─ Complex state with multiple sub-values and logic
│ │ └─ useReducer (actions + reducer = predictable transitions)
│ └─ Derived from existing state
│ └─ Calculate inline or useMemo — not useState
│
├─ Referencing a value WITHOUT triggering re-render
│ ├─ DOM element reference
│ │ └─ useRef<HTMLElement>(null) + ref={ref}
│ └─ Mutable value (timer ID, previous value, counter)
│ └─ useRef (mutate ref.current directly)
│
├─ Running a side effect
│ ├─ After every render (or specific deps)
│ │ ├─ Needs cleanup (subscription, timer, abort)
│ │ │ └─ useEffect with return cleanup function
│ │ └─ No cleanup (logging, analytics)
│ │ └─ useEffect with empty or dep array
│ ├─ Before browser paint (DOM mutation, animation)
│ │ └─ useLayoutEffect
│ └─ Triggered by user action (not render)
│ └─ Call it directly in the event handler — not useEffect
│
├─ Caching an expensive computation
│ └─ useMemo(() => expensiveCalc(a, b), [a, b])
│
├─ Stable callback reference for child props / event handlers
│ └─ useCallback(() => doThing(dep), [dep])
│
├─ Reading shared context value
│ └─ useContext(MyContext)
│
├─ Generating stable unique ID (forms, aria)
│ └─ useId()
│
├─ Syncing external store (Redux, Zustand internals)
│ └─ useSyncExternalStore(subscribe, getSnapshot)
│
└─ React 19+
├─ Await a promise or read context
│ └─ use(promise | context)
├─ Form submit state (pending, data, action)
│ └─ useFormStatus / useActionState
└─ Optimistic UI before server response
└─ useOptimistic(state, updateFn)
Component Pattern Decision Tree
What's your composition challenge?
│
├─ Group of related components sharing implicit state
│ (Tabs, Accordion, Select, Menu)
│ └─ Compound Components with Context
│ Parent provides state via Context
│ Children consume via useContext
│
├─ Consumer needs to control rendering output
│ └─ Render Props: children(props) or render={fn}
│ Good for: headless UI, flexible layouts
│
├─ Apply cross-cutting concerns (auth, logging, theming)
│ to multiple components
│ └─ Higher-Order Components (HOC)
│ Wrap with withAuth(Component) or withLogging(Component)
│ Prefer custom hooks for pure logic
│
├─ Encapsulate reusable stateful logic
│ └─ Custom Hook — always prefer over HOC when possible
│ Composable, testable, no wrapper hell
│
├─ Need imperative control from parent (focus, scroll, reset)
│ └─ forwardRef + useImperativeHandle
│
├─ Render content outside DOM hierarchy (modal, tooltip, toast)
│ └─ Portal: createPortal(content, document.body)
│
├─ Accept arbitrary children/slots without prop drilling
│ └─ Slot pattern via children, or named props (header, footer)
│
└─ Polymorphic rendering (button that renders as <a> or div)
└─ as prop pattern with TypeScript generics
State Management Decision Tree
Where does this state live and who owns it?
│
├─ Only one component needs it
│ └─ useState or useReducer (local state)
│
├─ A few nearby components need it
│ └─ Lift state to nearest common ancestor + prop drilling
│ (2-3 levels is fine)
│
├─ Many components need it, rarely changes
│ (theme, locale, auth user)
│ └─ React Context API
│ Split contexts by update frequency
│ Avoid single giant context
│
├─ Global client state, changes often
│ (shopping cart, UI preferences, navigation)
│ ├─ Simple/small app → Zustand (minimal boilerplate)
│ ├─ Atomic updates, React Suspense integration → Jotai
│ └─ Large team, time-travel debugging, complex logic → Redux Toolkit
│
├─ Server state (remote data, cache, sync)
│ (API data, database queries)
│ └─ TanStack Query (React Query)
│ Handles: caching, background refetch, loading/error
│ Don't use useState + useEffect for server data
│
└─ Form state
└─ React Hook Form + Zod validation
(controlled inputs are fine for simple forms)
React 19 Quick Reference
| Feature |
API |
Purpose |
use() hook |
use(promise) / use(context) |
Await promises in render, read context conditionally |
| Actions |
async function action(formData) |
Async transitions with built-in pending state |
useActionState |
useActionState(action, initialState) |
Action result + pending state |
useFormStatus |
useFormStatus() |
Pending/data/method inside form |
useOptimistic |
useOptimistic(state, updateFn) |
Optimistic UI before server response |
| React Compiler |
Automatic memoization |
Replaces most memo, useMemo, useCallback |
ref as prop |
<Input ref={ref}> |
No more forwardRef wrapper needed |
<Context> as provider |
<MyContext value={val}> |
No more <MyContext.Provider> |
// React 19: use() for data fetching in Server Components
import { use } from 'react';
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise); // suspends until resolved
return <h1>{user.name}</h1>;
}
// React 19: useActionState
import { useActionState } from 'react';
function ContactForm() {
const [state, action, isPending] = useActionState(
async (prevState: State, formData: FormData) => {
const result = await submitContact(formData);
return result;
},
{ error: null }
);
return (
<form action={action}>
<input name="email" type="email" />
<button disabled={isPending}>
{isPending ? 'Sending...' : 'Send'}
</button>
{state.error && <p>{state.error}</p>}
</form>
);
}
Server vs Client Components
Does this component need...?
│
├─ useState, useReducer, useContext
│ └─ Client Component ('use client')
│
├─ useEffect, useLayoutEffect
│ └─ Client Component ('use client')
│
├─ Browser APIs (window, document, localStorage)
│ └─ Client Component ('use client')
│
├─ Event handlers (onClick, onChange, onSubmit)
│ └─ Client Component ('use client')
│
├─ Third-party libraries that use hooks/browser APIs
│ └─ Client Component ('use client')
│
├─ Direct database/file system access
│ └─ Server Component (default, no directive)
│
├─ Access to env vars (server-only secrets)
│ └─ Server Component
│
├─ Large dependencies you want to keep off the client bundle
│ └─ Server Component
│
└─ async/await at the top level
└─ Server Component
Client boundary rules:
'use client' marks a boundary — everything imported below it becomes client JS
- Server Components can import Client Components (they pass as props/children)
- Client Components CANNOT import Server Components directly
- Pass Server Component output as
children prop to Client Components
- Server data → Client: pass as serializable props only (no functions, classes, DOM nodes)
Performance Checklist
| Technique |
When to Use |
When NOT to Use |
React.memo |
Component re-renders often with same props |
Nearly everything — adds comparison overhead |
useMemo |
Expensive calculation (>1ms), stable dep array |
Primitive values, simple expressions |
useCallback |
Callback passed to memoized child or in dep array |
Inline handlers on DOM elements |
React.lazy + Suspense |
Large components not needed on initial load |
Small components, SSR-critical content |
useTransition |
Non-urgent state updates (filtering, sorting) |
Time-sensitive UI (typing, hover) |
useDeferredValue |
Derived expensive render from fast-changing value |
Same as above |
| Virtualization |
Lists >100 items |
Small lists — overhead not worth it |
| React Compiler (v19) |
Automatic — replaces most manual memoization |
Opt-out with "use no memo" if needed |
Common Gotchas
| Gotcha |
Why It Happens |
Fix |
| Stale closure in useEffect |
Callback captures old state/prop at definition time |
Add value to dep array, or use functional update setState(prev => ...) |
| Missing useEffect dependency |
Linter disabled or ignored, stale data shown |
Never disable exhaustive-deps; use useCallback to stabilize functions |
| Index as list key |
Keys change on reorder/insert, causing wrong component identity |
Use stable unique ID from data (item.id) |
| Hydration mismatch |
Server HTML doesn't match first client render |
Avoid typeof window, random values, or dates in render; use useEffect for client-only content |
| Unnecessary re-renders from context |
All consumers re-render when any context value changes |
Split context by concern; memoize context value with useMemo |
| useEffect for derived state |
State derived from another state causes extra render cycle |
Compute derived value during render inline or with useMemo |
| Missing cleanup in useEffect |
Memory leaks from subscriptions, timers, fetch requests |
Always return cleanup function; use AbortController for fetch |
| Strict Mode double invocation |
Effects run twice in dev to catch bugs |
Design effects to be idempotent; cleanup must fully reverse effect |
| Controlled/uncontrolled switch |
value prop toggling between defined and undefined |
Always provide defined value or always use defaultValue; never both |
| Object/array in dep array |
New reference every render triggers effect repeatedly |
Memoize with useMemo; use primitive values in deps where possible |
| Async function directly in useEffect |
useEffect(() => async () => {}) returns a Promise, not cleanup |
Wrap: useEffect(() => { async function run() {...}; run(); }, []) |
Reference Files
| File |
When to Load |
./references/hooks-patterns.md |
Deep hook usage: custom hooks, React 19 hooks, useEffect patterns, hook composition |
./references/component-architecture.md |
Compound components, HOC, render props, portals, forwardRef, polymorphic components |
./references/state-management.md |
Context API, Zustand, Jotai, Redux Toolkit, TanStack Query, React Hook Form |
./references/server-components.md |
RSC architecture, Server Actions, Next.js App Router, caching, streaming, metadata |
./references/performance.md |
React.memo, code splitting, virtualization, React Compiler, Web Vitals, profiling |
./references/testing.md |
RTL queries, user-event, MSW, renderHook, Vitest setup, accessibility testing |
Staleness Verifier
This skill encodes fast-moving facts (the React 19 API surface, the ecosystem
package stack). scripts/check-react-facts.py
guards them against silent drift — internal consistency in PR CI, live
major-version drift in the scheduled freshness job:
# Structural (PR CI, no network): every catalogued package + React 19 gate is
# still named in this skill's prose, and the currency note still carries a year.
python3 skills/react-ops/scripts/check-react-facts.py --offline # exit 0 consistent, 10 drift
# Live (weekly freshness job, never blocks a PR): is any documented major
# now behind npm's latest dist-tag?
python3 skills/react-ops/scripts/check-react-facts.py --live # exit 10 a major moved ahead, 7 npm unreachable
The canonical fact list lives in assets/react-facts.json; when you add or drop a recommendation or the prose stops naming one, update it to match or --offline fails CI.
See Also
| Skill |
When to Combine |
typescript-ops |
TypeScript generics with React props, discriminated unions for state machines, utility types |
testing-ops |
Test strategy, mocking patterns, CI integration, snapshot vs behavioral tests |
tailwind-ops |
CSS-in-JS alternatives, responsive design with Tailwind in React components |
javascript-ops |
Async patterns, Promises, generators, module system fundamentals |
1---2name: react-ops3description: React development patterns, hooks, state management, Server Components, and performance optimization. Use for: react, hooks, useState, useEffect, jsx, tsx, next.js, nextjs, app router, server components, RSC, zustand, react query, component patterns, react testing library, error boundary, suspense, react 19.4license: MIT5---67# React Operations89Comprehensive React skill covering hooks, component architecture, state management, Server Components, and performance optimization.1011> React 19 ecosystem facts verified as of 2026-07.1213## Hook Selection Decision Tree1415```16What problem are you solving?17│18├─ Storing UI state that triggers re-renders19│ ├─ Simple value (string, number, boolean)20│ │ └─ useState21│ ├─ Complex state with multiple sub-values and logic22│ │ └─ useReducer (actions + reducer = predictable transitions)23│ └─ Derived from existing state24│ └─ Calculate inline or useMemo — not useState25│26├─ Referencing a value WITHOUT triggering re-render27│ ├─ DOM element reference28│ │ └─ useRef<HTMLElement>(null) + ref={ref}29│ └─ Mutable value (timer ID, previous value, counter)30│ └─ useRef (mutate ref.current directly)31│32├─ Running a side effect33│ ├─ After every render (or specific deps)34│ │ ├─ Needs cleanup (subscription, timer, abort)35│ │ │ └─ useEffect with return cleanup function36│ │ └─ No cleanup (logging, analytics)37│ │ └─ useEffect with empty or dep array38│ ├─ Before browser paint (DOM mutation, animation)39│ │ └─ useLayoutEffect40│ └─ Triggered by user action (not render)41│ └─ Call it directly in the event handler — not useEffect42│43├─ Caching an expensive computation44│ └─ useMemo(() => expensiveCalc(a, b), [a, b])45│46├─ Stable callback reference for child props / event handlers47│ └─ useCallback(() => doThing(dep), [dep])48│49├─ Reading shared context value50│ └─ useContext(MyContext)51│52├─ Generating stable unique ID (forms, aria)53│ └─ useId()54│55├─ Syncing external store (Redux, Zustand internals)56│ └─ useSyncExternalStore(subscribe, getSnapshot)57│58└─ React 19+59 ├─ Await a promise or read context60 │ └─ use(promise | context)61 ├─ Form submit state (pending, data, action)62 │ └─ useFormStatus / useActionState63 └─ Optimistic UI before server response64 └─ useOptimistic(state, updateFn)65```6667## Component Pattern Decision Tree6869```70What's your composition challenge?71│72├─ Group of related components sharing implicit state73│ (Tabs, Accordion, Select, Menu)74│ └─ Compound Components with Context75│ Parent provides state via Context76│ Children consume via useContext77│78├─ Consumer needs to control rendering output79│ └─ Render Props: children(props) or render={fn}80│ Good for: headless UI, flexible layouts81│82├─ Apply cross-cutting concerns (auth, logging, theming)83│ to multiple components84│ └─ Higher-Order Components (HOC)85│ Wrap with withAuth(Component) or withLogging(Component)86│ Prefer custom hooks for pure logic87│88├─ Encapsulate reusable stateful logic89│ └─ Custom Hook — always prefer over HOC when possible90│ Composable, testable, no wrapper hell91│92├─ Need imperative control from parent (focus, scroll, reset)93│ └─ forwardRef + useImperativeHandle94│95├─ Render content outside DOM hierarchy (modal, tooltip, toast)96│ └─ Portal: createPortal(content, document.body)97│98├─ Accept arbitrary children/slots without prop drilling99│ └─ Slot pattern via children, or named props (header, footer)100│101└─ Polymorphic rendering (button that renders as <a> or div)102 └─ as prop pattern with TypeScript generics103```104105## State Management Decision Tree106107```108Where does this state live and who owns it?109│110├─ Only one component needs it111│ └─ useState or useReducer (local state)112│113├─ A few nearby components need it114│ └─ Lift state to nearest common ancestor + prop drilling115│ (2-3 levels is fine)116│117├─ Many components need it, rarely changes118│ (theme, locale, auth user)119│ └─ React Context API120│ Split contexts by update frequency121│ Avoid single giant context122│123├─ Global client state, changes often124│ (shopping cart, UI preferences, navigation)125│ ├─ Simple/small app → Zustand (minimal boilerplate)126│ ├─ Atomic updates, React Suspense integration → Jotai127│ └─ Large team, time-travel debugging, complex logic → Redux Toolkit128│129├─ Server state (remote data, cache, sync)130│ (API data, database queries)131│ └─ TanStack Query (React Query)132│ Handles: caching, background refetch, loading/error133│ Don't use useState + useEffect for server data134│135└─ Form state136 └─ React Hook Form + Zod validation137 (controlled inputs are fine for simple forms)138```139140## React 19 Quick Reference141142| Feature | API | Purpose |143|---------|-----|---------|144| `use()` hook | `use(promise)` / `use(context)` | Await promises in render, read context conditionally |145| Actions | `async function action(formData)` | Async transitions with built-in pending state |146| `useActionState` | `useActionState(action, initialState)` | Action result + pending state |147| `useFormStatus` | `useFormStatus()` | Pending/data/method inside form |148| `useOptimistic` | `useOptimistic(state, updateFn)` | Optimistic UI before server response |149| React Compiler | Automatic memoization | Replaces most `memo`, `useMemo`, `useCallback` |150| `ref` as prop | `<Input ref={ref}>` | No more forwardRef wrapper needed |151| `<Context>` as provider | `<MyContext value={val}>` | No more `<MyContext.Provider>` |152153```tsx154// React 19: use() for data fetching in Server Components155import { use } from 'react';156157function UserProfile({ userPromise }: { userPromise: Promise<User> }) {158 const user = use(userPromise); // suspends until resolved159 return <h1>{user.name}</h1>;160}161162// React 19: useActionState163import { useActionState } from 'react';164165function ContactForm() {166 const [state, action, isPending] = useActionState(167 async (prevState: State, formData: FormData) => {168 const result = await submitContact(formData);169 return result;170 },171 { error: null }172 );173174 return (175 <form action={action}>176 <input name="email" type="email" />177 <button disabled={isPending}>178 {isPending ? 'Sending...' : 'Send'}179 </button>180 {state.error && <p>{state.error}</p>}181 </form>182 );183}184```185186## Server vs Client Components187188```189Does this component need...?190│191├─ useState, useReducer, useContext192│ └─ Client Component ('use client')193│194├─ useEffect, useLayoutEffect195│ └─ Client Component ('use client')196│197├─ Browser APIs (window, document, localStorage)198│ └─ Client Component ('use client')199│200├─ Event handlers (onClick, onChange, onSubmit)201│ └─ Client Component ('use client')202│203├─ Third-party libraries that use hooks/browser APIs204│ └─ Client Component ('use client')205│206├─ Direct database/file system access207│ └─ Server Component (default, no directive)208│209├─ Access to env vars (server-only secrets)210│ └─ Server Component211│212├─ Large dependencies you want to keep off the client bundle213│ └─ Server Component214│215└─ async/await at the top level216 └─ Server Component217```218219**Client boundary rules:**220- `'use client'` marks a boundary — everything imported below it becomes client JS221- Server Components can import Client Components (they pass as props/children)222- Client Components CANNOT import Server Components directly223- Pass Server Component output as `children` prop to Client Components224- Server data → Client: pass as serializable props only (no functions, classes, DOM nodes)225226## Performance Checklist227228| Technique | When to Use | When NOT to Use |229|-----------|-------------|-----------------|230| `React.memo` | Component re-renders often with same props | Nearly everything — adds comparison overhead |231| `useMemo` | Expensive calculation (>1ms), stable dep array | Primitive values, simple expressions |232| `useCallback` | Callback passed to memoized child or in dep array | Inline handlers on DOM elements |233| `React.lazy` + `Suspense` | Large components not needed on initial load | Small components, SSR-critical content |234| `useTransition` | Non-urgent state updates (filtering, sorting) | Time-sensitive UI (typing, hover) |235| `useDeferredValue` | Derived expensive render from fast-changing value | Same as above |236| Virtualization | Lists >100 items | Small lists — overhead not worth it |237| React Compiler (v19) | Automatic — replaces most manual memoization | Opt-out with `"use no memo"` if needed |238239## Common Gotchas240241| Gotcha | Why It Happens | Fix |242|--------|---------------|-----|243| Stale closure in useEffect | Callback captures old state/prop at definition time | Add value to dep array, or use functional update `setState(prev => ...)` |244| Missing useEffect dependency | Linter disabled or ignored, stale data shown | Never disable exhaustive-deps; use `useCallback` to stabilize functions |245| Index as list key | Keys change on reorder/insert, causing wrong component identity | Use stable unique ID from data (`item.id`) |246| Hydration mismatch | Server HTML doesn't match first client render | Avoid `typeof window`, random values, or dates in render; use `useEffect` for client-only content |247| Unnecessary re-renders from context | All consumers re-render when any context value changes | Split context by concern; memoize context value with `useMemo` |248| useEffect for derived state | State derived from another state causes extra render cycle | Compute derived value during render inline or with `useMemo` |249| Missing cleanup in useEffect | Memory leaks from subscriptions, timers, fetch requests | Always return cleanup function; use AbortController for fetch |250| Strict Mode double invocation | Effects run twice in dev to catch bugs | Design effects to be idempotent; cleanup must fully reverse effect |251| Controlled/uncontrolled switch | `value` prop toggling between defined and `undefined` | Always provide defined value or always use `defaultValue`; never both |252| Object/array in dep array | New reference every render triggers effect repeatedly | Memoize with `useMemo`; use primitive values in deps where possible |253| Async function directly in useEffect | `useEffect(() => async () => {})` returns a Promise, not cleanup | Wrap: `useEffect(() => { async function run() {...}; run(); }, [])` |254255## Reference Files256257| File | When to Load |258|------|-------------|259| `./references/hooks-patterns.md` | Deep hook usage: custom hooks, React 19 hooks, useEffect patterns, hook composition |260| `./references/component-architecture.md` | Compound components, HOC, render props, portals, forwardRef, polymorphic components |261| `./references/state-management.md` | Context API, Zustand, Jotai, Redux Toolkit, TanStack Query, React Hook Form |262| `./references/server-components.md` | RSC architecture, Server Actions, Next.js App Router, caching, streaming, metadata |263| `./references/performance.md` | React.memo, code splitting, virtualization, React Compiler, Web Vitals, profiling |264| `./references/testing.md` | RTL queries, user-event, MSW, renderHook, Vitest setup, accessibility testing |265266## Staleness Verifier267268This skill encodes fast-moving facts (the React 19 API surface, the ecosystem269package stack). [`scripts/check-react-facts.py`](scripts/check-react-facts.py)270guards them against silent drift — internal consistency in PR CI, live271major-version drift in the scheduled freshness job:272273```bash274# Structural (PR CI, no network): every catalogued package + React 19 gate is275# still named in this skill's prose, and the currency note still carries a year.276python3 skills/react-ops/scripts/check-react-facts.py --offline # exit 0 consistent, 10 drift277278# Live (weekly freshness job, never blocks a PR): is any documented major279# now behind npm's latest dist-tag?280python3 skills/react-ops/scripts/check-react-facts.py --live # exit 10 a major moved ahead, 7 npm unreachable281```282283The canonical fact list lives in [`assets/react-facts.json`](assets/react-facts.json); when you add or drop a recommendation or the prose stops naming one, update it to match or `--offline` fails CI.284285## See Also286287| Skill | When to Combine |288|-------|----------------|289| `typescript-ops` | TypeScript generics with React props, discriminated unions for state machines, utility types |290| `testing-ops` | Test strategy, mocking patterns, CI integration, snapshot vs behavioral tests |291| `tailwind-ops` | CSS-in-JS alternatives, responsive design with Tailwind in React components |292| `javascript-ops` | Async patterns, Promises, generators, module system fundamentals |