[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M70GYZ68WD3MKCXMHTWVNQ2H":3,"skill-same:01M70GYZ68WD3MKCXMHTWVNQ2H":73},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":51,"commitSha":52,"skillDirectory":53,"forksCount":54,"repositoryHtmlUrl":55,"repositoryHomepageUrl":6,"ownerHtmlUrl":56,"ownerBlogUrl":6,"ownerDescription":57,"updatedAt":58,"install":59,"resources":72},"01M70GYZ68WD3MKCXMHTWVNQ2H","react-patterns",null,"React 18\u002F19 patterns including hooks discipline, server\u002Fclient component boundaries, Suspense + error boundaries, form actions, data fetching, state management decision trees, and accessibility-first composition. Use when writing or reviewing React components.","mdnaimul22\u002Fhuman-skills","mdnaimul22","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F64520358?v=4","01M70FH95MH2H796Q01TYSFZ03","Naimul","01M70FH96798CQR4YHA13F17GA",3,"MIT","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Faffaan-m_ECC\u002Freact-patterns\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"react","patterns","react patterns","decision","when","data fetching","trees","error","form","hooks","discipline","use","writing","Composition","data","actions","server","component","react components","components","2026-11-10 10:12:30",false,"---\nname: react-patterns\ndescription: React 18\u002F19 patterns including hooks discipline, server\u002Fclient component boundaries, Suspense + error boundaries, form actions, data fetching, state management decision trees, and accessibility-first composition. Use when writing or reviewing React components.\nmetadata:\n  origin: ECC\n---\n\n# React Patterns\n\nIdiomatic React 18\u002F19 patterns for building robust, accessible, performant component trees.\n\n## When to Activate\n\n- Writing or modifying React function components, custom hooks, or component trees\n- Reviewing JSX\u002FTSX files\n- Designing state shape or component composition\n- Migrating class components or older `forwardRef`\u002F`useEffect`-heavy code\n- Choosing between local state, lifted state, context, and external stores\n- Working with Server Components \u002F Client Components (Next.js App Router, RSC)\n- Implementing forms with React 19 actions or controlled inputs\n- Wiring data fetching with TanStack Query \u002F SWR \u002F RSC\n\n## Core Principles\n\n### 1. Render is a Pure Function of Props and State\n\n```tsx\n\u002F\u002F Good: derive during render\nfunction Cart({ items }: { items: CartItem[] }) {\n  const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);\n  return \u003Cspan>{formatMoney(total)}\u003C\u002Fspan>;\n}\n\n\u002F\u002F Bad: derived state stored separately\nfunction Cart({ items }: { items: CartItem[] }) {\n  const [total, setTotal] = useState(0);\n  useEffect(() => {\n    setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));\n  }, [items]);\n  return \u003Cspan>{formatMoney(total)}\u003C\u002Fspan>;\n}\n```\n\nDerived state in `useEffect` adds a render cycle, can desync, and obscures the data flow.\n\n### 2. Side Effects Outside Render\n\nEffects, mutations, network calls, and subscriptions live in event handlers or `useEffect` — never in the render body.\n\n### 3. Composition Over Inheritance\n\nReact has no inheritance model for components. Compose with `children`, render props, or component props.\n\n## Hooks Discipline\n\nSee [rules\u002Freact\u002Fhooks.md](..\u002F..\u002Frules\u002Freact\u002Fhooks.md) for the full ruleset. Highlights:\n\n- Top-level only, never conditional\n- Cleanup every subscription, interval, listener\n- Functional updater (`setX(prev => prev + 1)`) when new state depends on old\n- Default position: do not memoize — add `useMemo`\u002F`useCallback` only when a profiler or a dependency chain proves it matters\n- Extract a custom hook only when the same hook sequence appears in 2+ components\n\n## State Location Decision Tree\n\n```\nUsed by one component?\n  -> useState inside it\n\nUsed by parent + a few descendants?\n  -> lift to nearest common ancestor\n\nUsed across distant branches AND low-frequency reads (theme, auth, locale)?\n  -> React Context\n\nHigh-frequency updates shared across the tree?\n  -> external store (Zustand, Jotai, Redux Toolkit)\n\nDerived from a server?\n  -> server-state library (TanStack Query, SWR, RSC fetch)\n```\n\nMost pages do not need context or a global store. Resist abstraction until duplicated lifting becomes painful.\n\n## Server \u002F Client Components (RSC)\n\n```tsx\n\u002F\u002F Server Component - default, async, never ships JS for itself\nexport default async function ProductPage({ params }: { params: { id: string } }) {\n  const product = await db.product.findUnique({ where: { id: params.id } });\n  if (!product) notFound();\n  return \u003CProductView product={product} \u002F>;\n}\n\n\u002F\u002F Client Component - opt in with \"use client\"\n\"use client\";\nexport function AddToCartButton({ productId }: { productId: string }) {\n  const [pending, startTransition] = useTransition();\n  return (\n    \u003Cbutton\n      disabled={pending}\n      onClick={() => startTransition(() => addToCart(productId))}\n    >\n      {pending ? \"Adding...\" : \"Add to cart\"}\n    \u003C\u002Fbutton>\n  );\n}\n```\n\nBoundaries:\n\n- Server -> Client: pass serializable props or `children`\n- Client -> Server: invoke Server Actions via `\u003Cform action={...}>` or imperatively from event handlers\n- Never `import` a Server Component from a Client Component file — compose them via `children` instead\n\n## Suspense + Error Boundaries\n\n```tsx\n\u003CErrorBoundary fallback={\u003CErrorView \u002F>}>\n  \u003CSuspense fallback={\u003CUserSkeleton \u002F>}>\n    \u003CUserDetail id={id} \u002F>\n  \u003C\u002FSuspense>\n\u003C\u002FErrorBoundary>\n```\n\n- Place Suspense boundaries close to the data, not at the route root — progressively reveal content\n- Error Boundary remains a class API; use `react-error-boundary` for a hook-friendly wrapper\n- A boundary catches errors thrown during render, lifecycle, and constructors of its children — NOT in event handlers or async code\n\n## Forms\n\n### React 19 form actions (preferred for new code)\n\n```tsx\n\"use client\";\nimport { useActionState } from \"react\";\n\nconst initial = { error: null as string | null };\n\nasync function updateUserAction(_prev: typeof initial, formData: FormData) {\n  \"use server\";\n  const parsed = UserSchema.safeParse(Object.fromEntries(formData));\n  if (!parsed.success) return { error: \"Invalid input\" };\n  await db.user.update({ where: { id: parsed.data.id }, data: parsed.data });\n  return { error: null };\n}\n\nexport function UserForm() {\n  const [state, formAction, pending] = useActionState(updateUserAction, initial);\n  return (\n    \u003Cform action={formAction}>\n      \u003Cinput name=\"name\" required \u002F>\n      \u003Cbutton type=\"submit\" disabled={pending}>Save\u003C\u002Fbutton>\n      {state.error && \u003Cp role=\"alert\">{state.error}\u003C\u002Fp>}\n    \u003C\u002Fform>\n  );\n}\n```\n\n### Controlled inputs\n\nUse controlled when the value drives other UI, formats on every keystroke, or implements real-time validation.\n\n### Complex forms\n\nFor multi-step forms, dynamic field arrays, or cross-field validation: use a library (React Hook Form, TanStack Form). Roll-your-own state management for forms past trivial complexity is a maintenance trap.\n\n## Data Fetching Decision Matrix\n\n| Need | Tool |\n|---|---|\n| Per-request data in Next.js App Router | RSC `await fetch()` |\n| Client-side cache + mutations + invalidation | TanStack Query |\n| Lightweight client cache + revalidation | SWR |\n| Real-time subscriptions | Server-Sent Events, WebSockets, or the lib's subscription API |\n| One-off fire-and-forget | `fetch()` in an event handler |\n\nAvoid `useEffect` + `fetch` for application data — race conditions, no cache, no retry, no Suspense integration.\n\n## Composition Recipes\n\n### Slot via `children`\n\n```tsx\n\u003CLayout>\n  \u003CHeader \u002F>\n  \u003CMain>{content}\u003C\u002FMain>\n\u003C\u002FLayout>\n```\n\n### Named slots\n\n```tsx\n\u003CPage header={\u003CNav \u002F>} sidebar={\u003CFilters \u002F>}>\n  \u003CResults \u002F>\n\u003C\u002FPage>\n```\n\n### Compound components (shared state via Context)\n\n```tsx\n\u003CTabs defaultValue=\"profile\">\n  \u003CTabs.List>\n    \u003CTabs.Trigger value=\"profile\">Profile\u003C\u002FTabs.Trigger>\n    \u003CTabs.Trigger value=\"settings\">Settings\u003C\u002FTabs.Trigger>\n  \u003C\u002FTabs.List>\n  \u003CTabs.Panel value=\"profile\">\u003CProfile \u002F>\u003C\u002FTabs.Panel>\n  \u003CTabs.Panel value=\"settings\">\u003CSettings \u002F>\u003C\u002FTabs.Panel>\n\u003C\u002FTabs>\n```\n\n### Render prop \u002F function-as-child\n\nUseful when the parent needs to pass parameters to the rendered output:\n\n```tsx\n\u003CDataLoader id={id}>\n  {({ data, isLoading }) => isLoading ? \u003CSpinner \u002F> : \u003CUserCard user={data} \u002F>}\n\u003C\u002FDataLoader>\n```\n\nModern alternative: a hook (`useData(id)`) returning the same shape — usually cleaner.\n\n## Performance\n\n### When `React.memo` Actually Helps\n\nWrap a component in `React.memo` only when:\n\n1. It re-renders frequently\n2. Its props are usually the same between renders\n3. Its render is measurably expensive\n\n`React.memo` adds an equality check on every render. If props differ on most renders, the check is pure overhead.\n\n### Avoiding Render Cascades\n\n- Lift state down rather than up where possible\n- Split context: one context per concern, so a change to `themeContext` does not re-render auth consumers\n- Use `useSyncExternalStore` for external state libraries — required for safe concurrent rendering\n\n### Lists\n\n- Provide stable `key` props (database id, not array index)\n- Virtualize long lists with `@tanstack\u002Freact-virtual` or `react-window` once visible item count exceeds ~50 with non-trivial rows\n\n## Accessibility-First Composition\n\n- Always render semantic HTML (`\u003Cbutton>`, `\u003Ca>`, `\u003Cnav>`, `\u003Cmain>`) before reaching for `role` attributes\n- Every interactive element must be reachable by keyboard\n- Form inputs need labels — `\u003Clabel htmlFor>` or `aria-label` if visually labeled by an icon\n- Manage focus on route changes and modal open\u002Fclose\n- Run `axe` in component tests (see [skills\u002Freact-testing](..\u002Freact-testing\u002FSKILL.md))\n- Cross-link: [skills\u002Faccessibility\u002FSKILL.md](..\u002Faccessibility\u002FSKILL.md) covers WCAG criteria and pattern libraries\n\n## Routing\n\nThis skill is router-agnostic. The patterns above work with React Router, TanStack Router, Next.js App Router, Remix Router. Router-specific patterns (loaders, actions, nested layouts) follow the router's documentation — those are framework concerns layered on top of React core.\n\n## Out of Scope (Pointer Sections)\n\n- **Next.js specifics**: App Router data loading, Route Handlers, Middleware, Parallel Routes — separate concern, use Next.js docs\n- **React Native**: Platform-specific patterns differ enough to warrant a separate `react-native-patterns` skill (not present yet)\n- **Remix**: Loader\u002Faction conventions overlap with RSC but follow Remix docs\n\n## Related\n\n- Rules: [rules\u002Freact\u002F](..\u002F..\u002Frules\u002Freact\u002F) — coding-style, hooks, patterns, security, testing\n- Skills: [react-performance](..\u002Freact-performance\u002FSKILL.md) for the Vercel-derived performance ruleset, [frontend-patterns](..\u002Ffrontend-patterns\u002FSKILL.md) for cross-framework UI concerns, [accessibility](..\u002Faccessibility\u002FSKILL.md), [angular-developer](..\u002Fangular-developer\u002FSKILL.md) for framework comparison\n- Agents: `react-reviewer` for code review, `react-build-resolver` for build\u002Fbundler errors\n- Commands: `\u002Freact-review`, `\u002Freact-build`, `\u002Freact-test`\n\n## Examples\n\n### Custom hook for debounced search\n\n```tsx\nfunction useDebounce\u003CT>(value: T, delay = 300): T {\n  const [debounced, setDebounced] = useState(value);\n  useEffect(() => {\n    const id = setTimeout(() => setDebounced(value), delay);\n    return () => clearTimeout(id);\n  }, [value, delay]);\n  return debounced;\n}\n\nfunction SearchBox() {\n  const [query, setQuery] = useState(\"\");\n  const debounced = useDebounce(query, 300);\n  const { data } = useQuery({\n    queryKey: [\"search\", debounced],\n    queryFn: () => searchApi(debounced),\n    enabled: debounced.length > 0,\n  });\n  return (\n    \u003C>\n      \u003Cinput value={query} onChange={(e) => setQuery(e.target.value)} \u002F>\n      \u003CResults items={data ?? []} \u002F>\n    \u003C\u002F>\n  );\n}\n```\n\n### Optimistic UI with React 19 `useOptimistic`\n\n```tsx\n\"use client\";\nimport { useOptimistic } from \"react\";\n\nexport function MessageList({ messages }: { messages: Message[] }) {\n  const [optimistic, addOptimistic] = useOptimistic(\n    messages,\n    (state, newMessage: Message) => [...state, newMessage],\n  );\n\n  async function send(formData: FormData) {\n    const text = String(formData.get(\"text\"));\n    addOptimistic({ id: \"pending\", text, sender: \"me\" });\n    await saveMessage(text);\n  }\n\n  return (\n    \u003C>\n      \u003Cul>{optimistic.map((m) => \u003Cli key={m.id}>{m.text}\u003C\u002Fli>)}\u003C\u002Ful>\n      \u003Cform action={send}>\n        \u003Cinput name=\"text\" \u002F>\n        \u003Cbutton type=\"submit\">Send\u003C\u002Fbutton>\n      \u003C\u002Fform>\n    \u003C\u002F>\n  );\n}\n```\n\n### Splitting context to avoid render cascades\n\n```tsx\n\u002F\u002F Two contexts: one rarely changes, one frequently\nconst ThemeContext = createContext\u003CTheme>(\"light\");\nconst NotificationsContext = createContext\u003CNotification[]>([]);\n\n\u002F\u002F A component that only consumes ThemeContext does NOT re-render when notifications change\n```\n","# React Patterns\n\nIdiomatic React 18\u002F19 patterns for building robust, accessible, performant component trees.\n\n## When to Activate\n\n- Writing or modifying React function components, custom hooks, or component trees\n- Reviewing JSX\u002FTSX files\n- Designing state shape or component composition\n- Migrating class components or older `forwardRef`\u002F`useEffect`-heavy code\n- Choosing between local state, lifted state, context, and external stores\n- Working with Server Components \u002F Client Components (Next.js App Router, RSC)\n- Implementing forms with React 19 actions or controlled inputs\n- Wiring data fetching with TanStack Query \u002F SWR \u002F RSC\n\n## Core Principles\n\n### 1. Render is a Pure Function of Props and State\n\n```tsx\n\u002F\u002F Good: derive during render\nfunction Cart({ items }: { items: CartItem[] }) {\n  const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);\n  return \u003Cspan>{formatMoney(total)}\u003C\u002Fspan>;\n}\n\n\u002F\u002F Bad: derived state stored separately\nfunction Cart({ items }: { items: CartItem[] }) {\n  const [total, setTotal] = useState(0);\n  useEffect(() => {\n    setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));\n  }, [items]);\n  return \u003Cspan>{formatMoney(total)}\u003C\u002Fspan>;\n}\n```\n\nDerived state in `useEffect` adds a render cycle, can desync, and obscures the data flow.\n\n### 2. Side Effects Outside Render\n\nEffects, mutations, network calls, and subscriptions live in event handlers or `useEffect` — never in the render body.\n\n### 3. Composition Over Inheritance\n\nReact has no inheritance model for components. Compose with `children`, render props, or component props.\n\n## Hooks Discipline\n\nSee [rules\u002Freact\u002Fhooks.md](..\u002F..\u002Frules\u002Freact\u002Fhooks.md) for the full ruleset. Highlights:\n\n- Top-level only, never conditional\n- Cleanup every subscription, interval, listener\n- Functional updater (`setX(prev => prev + 1)`) when new state depends on old\n- Default position: do not memoize — add `useMemo`\u002F`useCallback` only when a profiler or a dependency chain proves it matters\n- Extract a custom hook only when the same hook sequence appears in 2+ components\n\n## State Location Decision Tree\n\n```\nUsed by one component?\n  -> useState inside it\n\nUsed by parent + a few descendants?\n  -> lift to nearest common ancestor\n\nUsed across distant branches AND low-frequency reads (theme, auth, locale)?\n  -> React Context\n\nHigh-frequency updates shared across the tree?\n  -> external store (Zustand, Jotai, Redux Toolkit)\n\nDerived from a server?\n  -> server-state library (TanStack Query, SWR, RSC fetch)\n```\n\nMost pages do not need context or a global store. Resist abstraction until duplicated lifting becomes painful.\n\n## Server \u002F Client Components (RSC)\n\n```tsx\n\u002F\u002F Server Component - default, async, never ships JS for itself\nexport default async function ProductPage({ params }: { params: { id: string } }) {\n  const product = await db.product.findUnique({ where: { id: params.id } });\n  if (!product) notFound();\n  return \u003CProductView product={product} \u002F>;\n}\n\n\u002F\u002F Client Component - opt in with \"use client\"\n\"use client\";\nexport function AddToCartButton({ productId }: { productId: string }) {\n  const [pending, startTransition] = useTransition();\n  return (\n    \u003Cbutton\n      disabled={pending}\n      onClick={() => startTransition(() => addToCart(productId))}\n    >\n      {pending ? \"Adding...\" : \"Add to cart\"}\n    \u003C\u002Fbutton>\n  );\n}\n```\n\nBoundaries:\n\n- Server -> Client: pass serializable props or `children`\n- Client -> Server: invoke Server Actions via `\u003Cform action={...}>` or imperatively from event handlers\n- Never `import` a Server Component from a Client Component file — compose them via `children` instead\n\n## Suspense + Error Boundaries\n\n```tsx\n\u003CErrorBoundary fallback={\u003CErrorView \u002F>}>\n  \u003CSuspense fallback={\u003CUserSkeleton \u002F>}>\n    \u003CUserDetail id={id} \u002F>\n  \u003C\u002FSuspense>\n\u003C\u002FErrorBoundary>\n```\n\n- Place Suspense boundaries close to the data, not at the route root — progressively reveal content\n- Error Boundary remains a class API; use `react-error-boundary` for a hook-friendly wrapper\n- A boundary catches errors thrown during render, lifecycle, and constructors of its children — NOT in event handlers or async code\n\n## Forms\n\n### React 19 form actions (preferred for new code)\n\n```tsx\n\"use client\";\nimport { useActionState } from \"react\";\n\nconst initial = { error: null as string | null };\n\nasync function updateUserAction(_prev: typeof initial, formData: FormData) {\n  \"use server\";\n  const parsed = UserSchema.safeParse(Object.fromEntries(formData));\n  if (!parsed.success) return { error: \"Invalid input\" };\n  await db.user.update({ where: { id: parsed.data.id }, data: parsed.data });\n  return { error: null };\n}\n\nexport function UserForm() {\n  const [state, formAction, pending] = useActionState(updateUserAction, initial);\n  return (\n    \u003Cform action={formAction}>\n      \u003Cinput name=\"name\" required \u002F>\n      \u003Cbutton type=\"submit\" disabled={pending}>Save\u003C\u002Fbutton>\n      {state.error && \u003Cp role=\"alert\">{state.error}\u003C\u002Fp>}\n    \u003C\u002Fform>\n  );\n}\n```\n\n### Controlled inputs\n\nUse controlled when the value drives other UI, formats on every keystroke, or implements real-time validation.\n\n### Complex forms\n\nFor multi-step forms, dynamic field arrays, or cross-field validation: use a library (React Hook Form, TanStack Form). Roll-your-own state management for forms past trivial complexity is a maintenance trap.\n\n## Data Fetching Decision Matrix\n\n| Need | Tool |\n|---|---|\n| Per-request data in Next.js App Router | RSC `await fetch()` |\n| Client-side cache + mutations + invalidation | TanStack Query |\n| Lightweight client cache + revalidation | SWR |\n| Real-time subscriptions | Server-Sent Events, WebSockets, or the lib's subscription API |\n| One-off fire-and-forget | `fetch()` in an event handler |\n\nAvoid `useEffect` + `fetch` for application data — race conditions, no cache, no retry, no Suspense integration.\n\n## Composition Recipes\n\n### Slot via `children`\n\n```tsx\n\u003CLayout>\n  \u003CHeader \u002F>\n  \u003CMain>{content}\u003C\u002FMain>\n\u003C\u002FLayout>\n```\n\n### Named slots\n\n```tsx\n\u003CPage header={\u003CNav \u002F>} sidebar={\u003CFilters \u002F>}>\n  \u003CResults \u002F>\n\u003C\u002FPage>\n```\n\n### Compound components (shared state via Context)\n\n```tsx\n\u003CTabs defaultValue=\"profile\">\n  \u003CTabs.List>\n    \u003CTabs.Trigger value=\"profile\">Profile\u003C\u002FTabs.Trigger>\n    \u003CTabs.Trigger value=\"settings\">Settings\u003C\u002FTabs.Trigger>\n  \u003C\u002FTabs.List>\n  \u003CTabs.Panel value=\"profile\">\u003CProfile \u002F>\u003C\u002FTabs.Panel>\n  \u003CTabs.Panel value=\"settings\">\u003CSettings \u002F>\u003C\u002FTabs.Panel>\n\u003C\u002FTabs>\n```\n\n### Render prop \u002F function-as-child\n\nUseful when the parent needs to pass parameters to the rendered output:\n\n```tsx\n\u003CDataLoader id={id}>\n  {({ data, isLoading }) => isLoading ? \u003CSpinner \u002F> : \u003CUserCard user={data} \u002F>}\n\u003C\u002FDataLoader>\n```\n\nModern alternative: a hook (`useData(id)`) returning the same shape — usually cleaner.\n\n## Performance\n\n### When `React.memo` Actually Helps\n\nWrap a component in `React.memo` only when:\n\n1. It re-renders frequently\n2. Its props are usually the same between renders\n3. Its render is measurably expensive\n\n`React.memo` adds an equality check on every render. If props differ on most renders, the check is pure overhead.\n\n### Avoiding Render Cascades\n\n- Lift state down rather than up where possible\n- Split context: one context per concern, so a change to `themeContext` does not re-render auth consumers\n- Use `useSyncExternalStore` for external state libraries — required for safe concurrent rendering\n\n### Lists\n\n- Provide stable `key` props (database id, not array index)\n- Virtualize long lists with `@tanstack\u002Freact-virtual` or `react-window` once visible item count exceeds ~50 with non-trivial rows\n\n## Accessibility-First Composition\n\n- Always render semantic HTML (`\u003Cbutton>`, `\u003Ca>`, `\u003Cnav>`, `\u003Cmain>`) before reaching for `role` attributes\n- Every interactive element must be reachable by keyboard\n- Form inputs need labels — `\u003Clabel htmlFor>` or `aria-label` if visually labeled by an icon\n- Manage focus on route changes and modal open\u002Fclose\n- Run `axe` in component tests (see [skills\u002Freact-testing](..\u002Freact-testing\u002FSKILL.md))\n- Cross-link: [skills\u002Faccessibility\u002FSKILL.md](..\u002Faccessibility\u002FSKILL.md) covers WCAG criteria and pattern libraries\n\n## Routing\n\nThis skill is router-agnostic. The patterns above work with React Router, TanStack Router, Next.js App Router, Remix Router. Router-specific patterns (loaders, actions, nested layouts) follow the router's documentation — those are framework concerns layered on top of React core.\n\n## Out of Scope (Pointer Sections)\n\n- **Next.js specifics**: App Router data loading, Route Handlers, Middleware, Parallel Routes — separate concern, use Next.js docs\n- **React Native**: Platform-specific patterns differ enough to warrant a separate `react-native-patterns` skill (not present yet)\n- **Remix**: Loader\u002Faction conventions overlap with RSC but follow Remix docs\n\n## Related\n\n- Rules: [rules\u002Freact\u002F](..\u002F..\u002Frules\u002Freact\u002F) — coding-style, hooks, patterns, security, testing\n- Skills: [react-performance](..\u002Freact-performance\u002FSKILL.md) for the Vercel-derived performance ruleset, [frontend-patterns](..\u002Ffrontend-patterns\u002FSKILL.md) for cross-framework UI concerns, [accessibility](..\u002Faccessibility\u002FSKILL.md), [angular-developer](..\u002Fangular-developer\u002FSKILL.md) for framework comparison\n- Agents: `react-reviewer` for code review, `react-build-resolver` for build\u002Fbundler errors\n- Commands: `\u002Freact-review`, `\u002Freact-build`, `\u002Freact-test`\n\n## Examples\n\n### Custom hook for debounced search\n\n```tsx\nfunction useDebounce\u003CT>(value: T, delay = 300): T {\n  const [debounced, setDebounced] = useState(value);\n  useEffect(() => {\n    const id = setTimeout(() => setDebounced(value), delay);\n    return () => clearTimeout(id);\n  }, [value, delay]);\n  return debounced;\n}\n\nfunction SearchBox() {\n  const [query, setQuery] = useState(\"\");\n  const debounced = useDebounce(query, 300);\n  const { data } = useQuery({\n    queryKey: [\"search\", debounced],\n    queryFn: () => searchApi(debounced),\n    enabled: debounced.length > 0,\n  });\n  return (\n    \u003C>\n      \u003Cinput value={query} onChange={(e) => setQuery(e.target.value)} \u002F>\n      \u003CResults items={data ?? []} \u002F>\n    \u003C\u002F>\n  );\n}\n```\n\n### Optimistic UI with React 19 `useOptimistic`\n\n```tsx\n\"use client\";\nimport { useOptimistic } from \"react\";\n\nexport function MessageList({ messages }: { messages: Message[] }) {\n  const [optimistic, addOptimistic] = useOptimistic(\n    messages,\n    (state, newMessage: Message) => [...state, newMessage],\n  );\n\n  async function send(formData: FormData) {\n    const text = String(formData.get(\"text\"));\n    addOptimistic({ id: \"pending\", text, sender: \"me\" });\n    await saveMessage(text);\n  }\n\n  return (\n    \u003C>\n      \u003Cul>{optimistic.map((m) => \u003Cli key={m.id}>{m.text}\u003C\u002Fli>)}\u003C\u002Ful>\n      \u003Cform action={send}>\n        \u003Cinput name=\"text\" \u002F>\n        \u003Cbutton type=\"submit\">Send\u003C\u002Fbutton>\n      \u003C\u002Fform>\n    \u003C\u002F>\n  );\n}\n```\n\n### Splitting context to avoid render cascades\n\n```tsx\n\u002F\u002F Two contexts: one rarely changes, one frequently\nconst ThemeContext = createContext\u003CTheme>(\"light\");\nconst NotificationsContext = createContext\u003CNotification[]>([]);\n\n\u002F\u002F A component that only consumes ThemeContext does NOT re-render when notifications change\n```\n",{"name":5,"metadata":49,"description":7},{"origin":50},"ECC","main","74715deacdc69119d739f38916f8ee4f0f979ebf","skills\u002Fstorage\u002Faffaan-m_ECC\u002Freact-patterns",0,"https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills","https:\u002F\u002Fgithub.com\u002Fmdnaimul22","AI Agents Workflows Automation | Specializing in Python, FastAPI, MCP Agents & Custom ERP.","2026-11-11 21:16:57",{"source":8,"skillName":5,"commands":60,"prompt":70,"zipAvailable":71},[61,64,67],{"tool":62,"command":63},"npx","npx skills add mdnaimul22\u002Fhuman-skills --skill react-patterns",{"tool":65,"command":66},"bunx","bunx skills add mdnaimul22\u002Fhuman-skills --skill react-patterns",{"tool":68,"command":69},"pnpm","pnpm dlx skills add mdnaimul22\u002Fhuman-skills --skill react-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Faffaan-m_ECC\u002Freact-patterns\u002FSKILL.md",true,[],[74,82,109,131,150,165],{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":81},"01M72JT3YP1SQHD0MANGDXQ4RT","higgsfield-websites","Build, edit, and deploy full-stack websites, apps and games via the Higgsfield CLI (`higgsfield website …`). Each is a React 19 + TanStack Start SSR app in one Cloudflare Worker (D1\u002FR2\u002FKV\u002FDO\u002FContainers). THREE product types, picked via `--type` on create: `website` (standalone, no Higgsfield integration — references\u002Fwebsite-flow.md), `app` (Sign in with Higgsfield + fnf SDK, Quanta — references\u002Fapp-flow.md), `game` (realtime multiplayer rooms — references\u002Fgame-flow.md). Routes to the right flow; each carries its own rules and deploy\u002Fpublish gates.\nUse when: \"build me a website\", \"make a landing page\", \"create a web app\", \"build a SaaS dashboard \u002F portfolio\", \"make me a game\", \"deploy this site\", \"publish\". Also owns GAME ART: \"make a spritesheet\", \"tileable texture\", \"animate a 3D character\", game music\u002FSFX — see the game-* references.\nNOT for: single image\u002Fvideo\u002Faudio generation (higgsfield-generate), product photos (higgsfield-product-photoshoot), marketplace cards (higgsfield-marketplace-cards).","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fhiggsfield-ai_skills\u002Fhiggsfield-websites\u002FSKILL.md",[],[],"2026-11-10 22:39:42",{"skillNo":83,"name":84,"nameZh":6,"description":85,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":86,"categories":87,"keywords":102,"publishedAt":44},"01M70JPK8HAXXA85P7Q8NKZZYG","writing-skills","Use when creating new skills, editing existing skills, or verifying skills work before deployment","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fwriting-skills\u002FSKILL.md",[88,91,96,99],{"categoryNo":89,"categoryName":90,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":92,"categoryName":93,"taxonomyType":94,"taxonomyTypeDesc":95},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",{"categoryNo":97,"categoryName":98,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",[36,103,104,105,35,106,107,28,108],"writing skills","editing","deployment","Existing","new","work",{"skillNo":110,"name":111,"nameZh":6,"description":112,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":113,"categories":114,"keywords":120,"publishedAt":44},"01M70JP4PHBH1CQVVDWZ94W159","writing-plans","Use when you have a spec or requirements for a multi-step task, before touching code","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fwriting-plans\u002FSKILL.md",[115,116,117],{"categoryNo":89,"categoryName":90,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":92,"categoryName":93,"taxonomyType":94,"taxonomyTypeDesc":95},{"categoryNo":118,"categoryName":119,"taxonomyType":94,"taxonomyTypeDesc":95},"01JBSEEDCAT000000000000030","开发工程师",[36,121,122,123,28,124,125,35,126,127,128,129,130],"writing plans","plans","you","code","for","requirements","task","multi","spec","multi-step task",{"skillNo":132,"name":133,"nameZh":6,"description":134,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":135,"categories":136,"keywords":137,"publishedAt":44},"01M70JNTYVGXFZT8PASZ844TH8","verification-before-completion","Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fverification-before-completion\u002FSKILL.md",[],[138,139,140,141,142,143,35,144,145,146,28,147,108,148,149],"verification","verification before completion","complete","about","output","assertions","prs","evidence","claim","claims","commands","running",{"skillNo":151,"name":152,"nameZh":6,"description":153,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":154,"categories":155,"keywords":157,"publishedAt":44},"01M70JNGVSQDESWN0Q5BPJ875V","using-superpowers","Use when starting any conversation - establishes how to find and use skills, requiring skill invocation before ANY response including clarifying questions","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fusing-superpowers\u002FSKILL.md",[156],{"categoryNo":97,"categoryName":98,"taxonomyType":21,"taxonomyTypeDesc":22},[158,159,160,161,35,28,162,163,164],"using superpowers","superpowers","conversation","how to","how","find","questions",{"skillNo":166,"name":167,"nameZh":6,"description":168,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":169,"categories":170,"keywords":171,"publishedAt":44},"01M70JN57CE588NPYPYTWH1DWG","using-git-worktrees","Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fusing-git-worktrees\u002FSKILL.md",[],[172,173,174,175,176,177,28,108,178,179,180,181,182,183,184,122,35,185,186],"using git worktrees","Using git","git","worktrees","git worktrees","implementation","git worktree","worktree","native","Tools","isolation","fallback","feature","via","workspace"]