[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6D2GD6G65J4ZMM0ZB87DNGK":3,"skill-same:01M6D2GD6G65J4ZMM0ZB87DNGK":75},{"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":26,"publishedAt":47,"linkOnly":48,"markdownContent":49,"markdownBody":50,"frontMatter":51,"refName":52,"commitSha":53,"skillDirectory":54,"forksCount":55,"repositoryHtmlUrl":56,"repositoryHomepageUrl":57,"ownerHtmlUrl":58,"ownerBlogUrl":57,"ownerDescription":59,"updatedAt":60,"install":61,"resources":74},"01M6D2GD6G65J4ZMM0ZB87DNGK","ui-stack-react",null,"Building interfaces in React 19: file layout by feature, typed function components, composition and slots, variants with cva, native props and refs passed through, where state lives and derived values, effects only for outside systems, keys, context split by concern, server data with TanStack Query, forms with actions or react-hook-form, Suspense and error boundaries, transitions and deferred values, portals and headless accessible primitives, the React Compiler and memo, Testing Library, and the traps (stale closures, racing effects, props copied into state, index keys, giant components). Read when the project uses React.","enowdev\u002Fenowxcli","enowdev","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F179420349?v=4","01M6D1MVX2KC0X3Z9NESE7P22K","Enow Dev.","01M6D1MW1FMZGBDKWAJY4GQX81",67,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-react\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",[27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],"stack","ui stack react","ui","react","read","components","layout","project","query","forms","tanstack query","only","primitives","testing library","testing","Composition","data","actions","server","library","2026-11-10 10:12:30",false,"---\nname: ui-stack-react\ndescription: \"Building interfaces in React 19: file layout by feature, typed function components, composition and slots, variants with cva, native props and refs passed through, where state lives and derived values, effects only for outside systems, keys, context split by concern, server data with TanStack Query, forms with actions or react-hook-form, Suspense and error boundaries, transitions and deferred values, portals and headless accessible primitives, the React Compiler and memo, Testing Library, and the traps (stale closures, racing effects, props copied into state, index keys, giant components). Read when the project uses React.\"\n---\n\n# React\n\nThe generated React component is one 400-line file with a boolean prop per\nvariation, an effect that copies props into state and another that fetches,\n`key={index}`, `useMemo` on everything, a clickable `div`, and one context\nholding the whole app. This is React 19 (19.3 in September 2026) as\nexperienced teams write it: small typed components that compose, state where\nit belongs, effects only at the edges. Next.js is in `ui-stack-next`;\napp-wide state and data in `frontend-state` and `frontend-data`.\n\n## 1. Project layout\n\n```text\nsrc\u002Fmain.tsx  src\u002FApp.tsx     entry, providers, router\nsrc\u002Froutes\u002F                   one file per screen (or the router's layout)\nsrc\u002Fcomponents\u002Fui\u002F            primitives: Button, Input, Field, Dialog\nsrc\u002Ffeatures\u002Forders\u002F          OrderTable.tsx, OrderFilters.tsx, useOrders.ts,\n                              orders.api.ts, OrderTable.test.tsx\nsrc\u002Flib\u002F                      the API client, formatting, cn\n```\n\n- Files by feature, not by kind: everything about orders sits together, and\n  removing a feature removes a folder. Primitives in `components\u002Fui\u002F`,\n  composed parts in `features\u002F\u003Cfeature>\u002F` (or `components\u002F\u003Cfeature>\u002F`), or\n  the project's own layout.\n- A new app: Vite (`npm create vite@latest app -- --template react-ts`), a\n  router (React Router or TanStack Router) and TanStack Query; Next.js when\n  pages need server rendering or search traffic (`ui-stack-next`).\n\n## 2. Components\n\n- Function components, one per file named for it, props typed (a `type` or\n  `interface`); match the project's export style.\n- Composition over configuration: `children` and named slots (`icon`,\n  `actions` props that take elements) rather than a boolean per variation\n  (`showIcon`, `isCompact`). Variants as a `variant` and a `size` prop.\n- A primitive passes native props and the `ref` through, so it stays a real\n  button to forms and screen readers. In 19 `ref` is an ordinary prop;\n  `forwardRef` only before 19.\n\n```tsx\nimport type { ComponentProps } from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@\u002Flib\u002Fcn\";\n\nconst button = cva(\n  \"inline-flex items-center justify-center gap-2 rounded-md font-medium disabled:opacity-50\",\n  {\n    variants: {\n      variant: { primary: \"bg-accent text-on-accent\", quiet: \"text-ink hover:bg-surface-2\" },\n      size: { sm: \"h-8 px-3 text-sm\", md: \"h-10 px-4\" },\n    },\n    defaultVariants: { variant: \"primary\", size: \"md\" },\n  },\n);\ntype ButtonProps = ComponentProps\u003C\"button\"> & VariantProps\u003Ctypeof button>;\nexport function Button({ variant, size, className, type = \"button\", ...props }: ButtonProps) {\n  return \u003Cbutton type={type} className={cn(button({ variant, size }), className)} {...props} \u002F>;\n}\n```\n\n- `type=\"button\"` by default: a button inside a form otherwise submits it.\n  Without Tailwind, the same shape with a class map from a CSS Module.\n- Never define a component inside another's render: it remounts every time\n  and loses its state and focus.\n- A component past about 200 lines or doing two jobs splits: its data into\n  a hook, each region into its own component.\n\n## 3. State\n\n- State in the lowest component that needs it; lift it when two need it;\n  context for what many parts read (theme, the signed-in user), not for\n  everything.\n- Derive values during render instead of copying them into state and\n  syncing with an effect: a filtered list is `items.filter(...)` in render.\n- State that should reset when a record changes: `key={record.id}` on the\n  component, not an effect that clears it.\n- Context split by concern (the user, the theme, one per compound\n  component), its value stable; in 19 the context is the provider\n  (`\u003CThemeContext value={theme}>`). A value that changes often (form input,\n  the pointer) re-renders every reader: keep it local, or in a store with\n  selectors (Zustand, `useSyncExternalStore`).\n- Filters, tabs and the page number live in the URL through the router, so\n  reload and share keep them.\n\n## 4. Effects and events\n\n- Effects only to synchronise with something outside React (a\n  subscription, a socket, a third-party widget, the document title), with a\n  cleanup. Strict Mode runs them twice in development to prove the cleanup.\n- What a click causes belongs in the click handler, not in an effect\n  watching state the handler set.\n- An effect that needs the latest props without re-running reads them\n  through `useEffectEvent` (19.2):\n\n```tsx\nconst onMessage = useEffectEvent((message: Message) => {\n  if (!muted) notify(message); \u002F\u002F always the current `muted`\n});\nuseEffect(() => {\n  const socket = connect(roomId);\n  socket.on(\"message\", onMessage);\n  return () => socket.disconnect();\n}, [roomId]); \u002F\u002F reconnects only when the room changes\n```\n\n## 5. Server data\n\n- Through the project's library (TanStack Query, SWR) or the framework's\n  loaders; never a hand-written fetch in an effect when one of those is\n  there. Loading, error and empty are three branches in the markup, each\n  saying what to do:\n\n```tsx\nconst orders = useQuery({\n  queryKey: [\"orders\", filters],\n  queryFn: ({ signal }) => api.listOrders(filters, { signal }),\n});\nif (orders.isPending) return \u003COrdersSkeleton \u002F>;\nif (orders.isError) return \u003CLoadError what=\"orders\" onRetry={() => orders.refetch()} \u002F>;\nif (orders.data.length === 0) return \u003CNoOrders \u002F>;\nreturn \u003COrderTable orders={orders.data} \u002F>;\n```\n\n- The key holds every input, so a filter change refetches and caches;\n  after a mutation, `queryClient.invalidateQueries({ queryKey: [\"orders\"] })`.\n- Without a library, a fetch in an effect aborts in its cleanup\n  (`AbortController`), or the slower of two responses lands last and wins.\n\n## 6. Lists and forms\n\n- Keys are stable ids from the data, never the index for a list that\n  changes: with index keys, row state and focus stay at the position while\n  the data moves. Hundreds of rows are virtualised (TanStack Virtual) or\n  paginated.\n- Labels tied to fields with `htmlFor` and an id from `useId`; the error in\n  words under the field with `aria-describedby`; the submit button disabled\n  and showing progress while pending (`frontend-forms`).\n- Use the project's form library (react-hook-form, with zod when it is\n  there) for large forms and validation as the user types:\n\n```tsx\nconst form = useForm\u003CInvite>({ resolver: zodResolver(inviteSchema), defaultValues: { email: \"\" } });\nconst id = useId();\nconst error = form.formState.errors.email;\nreturn (\n  \u003Cform onSubmit={form.handleSubmit(sendInvite)} noValidate>\n    \u003Clabel htmlFor={id}>Work email\u003C\u002Flabel>\n    \u003Cinput id={id} type=\"email\" autoComplete=\"email\" {...form.register(\"email\")}\n      aria-invalid={!!error} aria-describedby={`${id}-error`} \u002F>\n    \u003Cp id={`${id}-error`}>{error?.message}\u003C\u002Fp>\n    \u003Cbutton type=\"submit\" disabled={form.formState.isSubmitting}>Send invite\u003C\u002Fbutton>\n  \u003C\u002Fform>\n);\n```\n\n- A form that posts can use React 19 actions: `\u003Cform action={fn}>`,\n  `useActionState` for the result and a pending flag, `useFormStatus` in a\n  child of the form, `useOptimistic` for a list that updates at once. React\n  resets an uncontrolled form after its action: on a failure, return the\n  submitted values and use them as `defaultValue`.\n\n## 7. Suspense, errors and transitions\n\n- `\u003CSuspense fallback={\u003CSkeleton \u002F>}>` around the part that waits (a lazy\n  component, `use(promise)`, a suspense query), where the layout should\n  hold, not around the whole app.\n- Error boundaries around regions that can fail alone (a chart, a panel),\n  with a retry: `react-error-boundary`'s `\u003CErrorBoundary>`, since a\n  boundary is otherwise a class component (`frontend-errors`).\n- `useTransition` for an update that renders a lot (a tab switch, a filter\n  on a big table): the old screen stays usable and `isPending` dims it.\n  `useDeferredValue` for a fast-typed value feeding something slow:\n  `\u003CResults query={deferredQuery} dimmed={query !== deferredQuery} \u002F>`.\n- `\u003CActivity mode=\"hidden\">` (19.2) keeps a hidden tab's state and pauses\n  its effects instead of unmounting it.\n\n## 8. Accessibility and overlays\n\n- Semantic elements, never a clickable `div`. Dialogs, menus, tabs,\n  popovers, tooltips and comboboxes from the project's primitives (Radix,\n  Base UI, React Aria, or shadcn\u002Fui built on one), which handle focus, keys\n  and ARIA, rather than hand-rolled.\n- Overlays render in a portal (`createPortal(node, document.body)`, which\n  the libraries do) or the native top layer (`\u003Cdialog>`, `popover`), so no\n  `overflow: hidden` parent clips them and no `z-index` war starts.\n- Focus moves into a dialog and back to its trigger on close; after a route\n  change, focus goes to the new page's `h1` (`frontend-accessibility`).\n- `\u003Ctitle>` and `\u003Cmeta>` rendered in a component are hoisted to `\u003Chead>` in\n  19: a title per screen without a helmet library.\n\n## 9. Performance\n\n- Memoise after measuring, not by habit. With the React Compiler (1.0) on,\n  components and values are memoised for you: add no `useMemo`,\n  `useCallback` or `memo` by habit, and keep existing ones until a test\n  shows they can go.\n- Turning the compiler on: with `@vitejs\u002Fplugin-react` 6, add\n  `babel({ presets: [reactCompilerPreset()] })` beside `react()` (`babel`\n  from `@rolldown\u002Fplugin-babel`, the preset from `@vitejs\u002Fplugin-react`);\n  earlier versions take `react({ babel: { plugins:\n  [\"babel-plugin-react-compiler\"] } })`; Next.js has `reactCompiler: true`.\n  Its lint rules come with the latest `eslint-plugin-react-hooks`.\n- Split heavy routes and components with `lazy` and a Suspense fallback;\n  import one function, not a library's barrel. Images with sizes set\n  (`frontend-performance`).\n\n## 10. Testing\n\nBehaviour as a user meets it: Vitest with Testing Library\n(`@testing-library\u002Freact`, `user-event`, `jest-dom`), queries by role and\nname, never by class or test id first (`frontend-testing`).\n\n```tsx\ntest(\"filters orders by status\", async () => {\n  const user = userEvent.setup();\n  render(\u003COrders initial={orders} \u002F>);\n  await user.click(screen.getByRole(\"button\", { name: \"Paid\" }));\n  expect(screen.getAllByRole(\"row\")).toHaveLength(3); \u002F\u002F the header and two paid\n});\n```\n\n## 11. Traps\n\n- Stale closures: an interval or handler reading state from the render that\n  made it. Use the updater (`setCount((c) => c + 1)`), a ref, or\n  `useEffectEvent`.\n- Racing effects: two requests for two ids, the older landing last. Abort\n  in the cleanup, or let the data library handle it.\n- Props copied into state (`useState(props.user)`) never update: derive,\n  or reset with `key`.\n- `{count && \u003CBadge \u002F>}` renders `0`: write `count > 0 &&`.\n- Ten `useState`s that change together: one `useReducer`, or split the\n  component.\n- Prop drilling answered with a context for everything: pass `children`\n  and elements instead, so the middle layers never see the data.\n\n## Check it\n\n- `npm run build` (a Vite template runs `tsc -b && vite build`), `npm run\n  lint` when there is one, `npx vitest run`.\n- With Vite, look at the result with the `preview` tool: `url`\n  `http:\u002F\u002Flocalhost:5173\u002F` and `start` `npm run dev -- --port 5173\n  --strictPort`. With Next, see `ui-stack-next`.\n- The console in the `preview` report: key warnings, hydration errors and\n  failed requests are bugs, not noise.\n\n## Avoid\n\nA boolean prop per variation; components defined inside components; state\ncopied from props; effects for derived values or for events; fetch in an\neffect beside a data library; index keys on changing lists; `useMemo` by\nhabit; a context for everything; a clickable `div`; a hand-rolled dialog,\nmenu or combobox; overlays clipped by a parent; `{count && ...}`.\n","# React\n\nThe generated React component is one 400-line file with a boolean prop per\nvariation, an effect that copies props into state and another that fetches,\n`key={index}`, `useMemo` on everything, a clickable `div`, and one context\nholding the whole app. This is React 19 (19.3 in September 2026) as\nexperienced teams write it: small typed components that compose, state where\nit belongs, effects only at the edges. Next.js is in `ui-stack-next`;\napp-wide state and data in `frontend-state` and `frontend-data`.\n\n## 1. Project layout\n\n```text\nsrc\u002Fmain.tsx  src\u002FApp.tsx     entry, providers, router\nsrc\u002Froutes\u002F                   one file per screen (or the router's layout)\nsrc\u002Fcomponents\u002Fui\u002F            primitives: Button, Input, Field, Dialog\nsrc\u002Ffeatures\u002Forders\u002F          OrderTable.tsx, OrderFilters.tsx, useOrders.ts,\n                              orders.api.ts, OrderTable.test.tsx\nsrc\u002Flib\u002F                      the API client, formatting, cn\n```\n\n- Files by feature, not by kind: everything about orders sits together, and\n  removing a feature removes a folder. Primitives in `components\u002Fui\u002F`,\n  composed parts in `features\u002F\u003Cfeature>\u002F` (or `components\u002F\u003Cfeature>\u002F`), or\n  the project's own layout.\n- A new app: Vite (`npm create vite@latest app -- --template react-ts`), a\n  router (React Router or TanStack Router) and TanStack Query; Next.js when\n  pages need server rendering or search traffic (`ui-stack-next`).\n\n## 2. Components\n\n- Function components, one per file named for it, props typed (a `type` or\n  `interface`); match the project's export style.\n- Composition over configuration: `children` and named slots (`icon`,\n  `actions` props that take elements) rather than a boolean per variation\n  (`showIcon`, `isCompact`). Variants as a `variant` and a `size` prop.\n- A primitive passes native props and the `ref` through, so it stays a real\n  button to forms and screen readers. In 19 `ref` is an ordinary prop;\n  `forwardRef` only before 19.\n\n```tsx\nimport type { ComponentProps } from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@\u002Flib\u002Fcn\";\n\nconst button = cva(\n  \"inline-flex items-center justify-center gap-2 rounded-md font-medium disabled:opacity-50\",\n  {\n    variants: {\n      variant: { primary: \"bg-accent text-on-accent\", quiet: \"text-ink hover:bg-surface-2\" },\n      size: { sm: \"h-8 px-3 text-sm\", md: \"h-10 px-4\" },\n    },\n    defaultVariants: { variant: \"primary\", size: \"md\" },\n  },\n);\ntype ButtonProps = ComponentProps\u003C\"button\"> & VariantProps\u003Ctypeof button>;\nexport function Button({ variant, size, className, type = \"button\", ...props }: ButtonProps) {\n  return \u003Cbutton type={type} className={cn(button({ variant, size }), className)} {...props} \u002F>;\n}\n```\n\n- `type=\"button\"` by default: a button inside a form otherwise submits it.\n  Without Tailwind, the same shape with a class map from a CSS Module.\n- Never define a component inside another's render: it remounts every time\n  and loses its state and focus.\n- A component past about 200 lines or doing two jobs splits: its data into\n  a hook, each region into its own component.\n\n## 3. State\n\n- State in the lowest component that needs it; lift it when two need it;\n  context for what many parts read (theme, the signed-in user), not for\n  everything.\n- Derive values during render instead of copying them into state and\n  syncing with an effect: a filtered list is `items.filter(...)` in render.\n- State that should reset when a record changes: `key={record.id}` on the\n  component, not an effect that clears it.\n- Context split by concern (the user, the theme, one per compound\n  component), its value stable; in 19 the context is the provider\n  (`\u003CThemeContext value={theme}>`). A value that changes often (form input,\n  the pointer) re-renders every reader: keep it local, or in a store with\n  selectors (Zustand, `useSyncExternalStore`).\n- Filters, tabs and the page number live in the URL through the router, so\n  reload and share keep them.\n\n## 4. Effects and events\n\n- Effects only to synchronise with something outside React (a\n  subscription, a socket, a third-party widget, the document title), with a\n  cleanup. Strict Mode runs them twice in development to prove the cleanup.\n- What a click causes belongs in the click handler, not in an effect\n  watching state the handler set.\n- An effect that needs the latest props without re-running reads them\n  through `useEffectEvent` (19.2):\n\n```tsx\nconst onMessage = useEffectEvent((message: Message) => {\n  if (!muted) notify(message); \u002F\u002F always the current `muted`\n});\nuseEffect(() => {\n  const socket = connect(roomId);\n  socket.on(\"message\", onMessage);\n  return () => socket.disconnect();\n}, [roomId]); \u002F\u002F reconnects only when the room changes\n```\n\n## 5. Server data\n\n- Through the project's library (TanStack Query, SWR) or the framework's\n  loaders; never a hand-written fetch in an effect when one of those is\n  there. Loading, error and empty are three branches in the markup, each\n  saying what to do:\n\n```tsx\nconst orders = useQuery({\n  queryKey: [\"orders\", filters],\n  queryFn: ({ signal }) => api.listOrders(filters, { signal }),\n});\nif (orders.isPending) return \u003COrdersSkeleton \u002F>;\nif (orders.isError) return \u003CLoadError what=\"orders\" onRetry={() => orders.refetch()} \u002F>;\nif (orders.data.length === 0) return \u003CNoOrders \u002F>;\nreturn \u003COrderTable orders={orders.data} \u002F>;\n```\n\n- The key holds every input, so a filter change refetches and caches;\n  after a mutation, `queryClient.invalidateQueries({ queryKey: [\"orders\"] })`.\n- Without a library, a fetch in an effect aborts in its cleanup\n  (`AbortController`), or the slower of two responses lands last and wins.\n\n## 6. Lists and forms\n\n- Keys are stable ids from the data, never the index for a list that\n  changes: with index keys, row state and focus stay at the position while\n  the data moves. Hundreds of rows are virtualised (TanStack Virtual) or\n  paginated.\n- Labels tied to fields with `htmlFor` and an id from `useId`; the error in\n  words under the field with `aria-describedby`; the submit button disabled\n  and showing progress while pending (`frontend-forms`).\n- Use the project's form library (react-hook-form, with zod when it is\n  there) for large forms and validation as the user types:\n\n```tsx\nconst form = useForm\u003CInvite>({ resolver: zodResolver(inviteSchema), defaultValues: { email: \"\" } });\nconst id = useId();\nconst error = form.formState.errors.email;\nreturn (\n  \u003Cform onSubmit={form.handleSubmit(sendInvite)} noValidate>\n    \u003Clabel htmlFor={id}>Work email\u003C\u002Flabel>\n    \u003Cinput id={id} type=\"email\" autoComplete=\"email\" {...form.register(\"email\")}\n      aria-invalid={!!error} aria-describedby={`${id}-error`} \u002F>\n    \u003Cp id={`${id}-error`}>{error?.message}\u003C\u002Fp>\n    \u003Cbutton type=\"submit\" disabled={form.formState.isSubmitting}>Send invite\u003C\u002Fbutton>\n  \u003C\u002Fform>\n);\n```\n\n- A form that posts can use React 19 actions: `\u003Cform action={fn}>`,\n  `useActionState` for the result and a pending flag, `useFormStatus` in a\n  child of the form, `useOptimistic` for a list that updates at once. React\n  resets an uncontrolled form after its action: on a failure, return the\n  submitted values and use them as `defaultValue`.\n\n## 7. Suspense, errors and transitions\n\n- `\u003CSuspense fallback={\u003CSkeleton \u002F>}>` around the part that waits (a lazy\n  component, `use(promise)`, a suspense query), where the layout should\n  hold, not around the whole app.\n- Error boundaries around regions that can fail alone (a chart, a panel),\n  with a retry: `react-error-boundary`'s `\u003CErrorBoundary>`, since a\n  boundary is otherwise a class component (`frontend-errors`).\n- `useTransition` for an update that renders a lot (a tab switch, a filter\n  on a big table): the old screen stays usable and `isPending` dims it.\n  `useDeferredValue` for a fast-typed value feeding something slow:\n  `\u003CResults query={deferredQuery} dimmed={query !== deferredQuery} \u002F>`.\n- `\u003CActivity mode=\"hidden\">` (19.2) keeps a hidden tab's state and pauses\n  its effects instead of unmounting it.\n\n## 8. Accessibility and overlays\n\n- Semantic elements, never a clickable `div`. Dialogs, menus, tabs,\n  popovers, tooltips and comboboxes from the project's primitives (Radix,\n  Base UI, React Aria, or shadcn\u002Fui built on one), which handle focus, keys\n  and ARIA, rather than hand-rolled.\n- Overlays render in a portal (`createPortal(node, document.body)`, which\n  the libraries do) or the native top layer (`\u003Cdialog>`, `popover`), so no\n  `overflow: hidden` parent clips them and no `z-index` war starts.\n- Focus moves into a dialog and back to its trigger on close; after a route\n  change, focus goes to the new page's `h1` (`frontend-accessibility`).\n- `\u003Ctitle>` and `\u003Cmeta>` rendered in a component are hoisted to `\u003Chead>` in\n  19: a title per screen without a helmet library.\n\n## 9. Performance\n\n- Memoise after measuring, not by habit. With the React Compiler (1.0) on,\n  components and values are memoised for you: add no `useMemo`,\n  `useCallback` or `memo` by habit, and keep existing ones until a test\n  shows they can go.\n- Turning the compiler on: with `@vitejs\u002Fplugin-react` 6, add\n  `babel({ presets: [reactCompilerPreset()] })` beside `react()` (`babel`\n  from `@rolldown\u002Fplugin-babel`, the preset from `@vitejs\u002Fplugin-react`);\n  earlier versions take `react({ babel: { plugins:\n  [\"babel-plugin-react-compiler\"] } })`; Next.js has `reactCompiler: true`.\n  Its lint rules come with the latest `eslint-plugin-react-hooks`.\n- Split heavy routes and components with `lazy` and a Suspense fallback;\n  import one function, not a library's barrel. Images with sizes set\n  (`frontend-performance`).\n\n## 10. Testing\n\nBehaviour as a user meets it: Vitest with Testing Library\n(`@testing-library\u002Freact`, `user-event`, `jest-dom`), queries by role and\nname, never by class or test id first (`frontend-testing`).\n\n```tsx\ntest(\"filters orders by status\", async () => {\n  const user = userEvent.setup();\n  render(\u003COrders initial={orders} \u002F>);\n  await user.click(screen.getByRole(\"button\", { name: \"Paid\" }));\n  expect(screen.getAllByRole(\"row\")).toHaveLength(3); \u002F\u002F the header and two paid\n});\n```\n\n## 11. Traps\n\n- Stale closures: an interval or handler reading state from the render that\n  made it. Use the updater (`setCount((c) => c + 1)`), a ref, or\n  `useEffectEvent`.\n- Racing effects: two requests for two ids, the older landing last. Abort\n  in the cleanup, or let the data library handle it.\n- Props copied into state (`useState(props.user)`) never update: derive,\n  or reset with `key`.\n- `{count && \u003CBadge \u002F>}` renders `0`: write `count > 0 &&`.\n- Ten `useState`s that change together: one `useReducer`, or split the\n  component.\n- Prop drilling answered with a context for everything: pass `children`\n  and elements instead, so the middle layers never see the data.\n\n## Check it\n\n- `npm run build` (a Vite template runs `tsc -b && vite build`), `npm run\n  lint` when there is one, `npx vitest run`.\n- With Vite, look at the result with the `preview` tool: `url`\n  `http:\u002F\u002Flocalhost:5173\u002F` and `start` `npm run dev -- --port 5173\n  --strictPort`. With Next, see `ui-stack-next`.\n- The console in the `preview` report: key warnings, hydration errors and\n  failed requests are bugs, not noise.\n\n## Avoid\n\nA boolean prop per variation; components defined inside components; state\ncopied from props; effects for derived values or for events; fetch in an\neffect beside a data library; index keys on changing lists; `useMemo` by\nhabit; a context for everything; a clickable `div`; a hand-rolled dialog,\nmenu or combobox; overlays clipped by a parent; `{count && ...}`.\n",{"name":5,"description":7},"main","38293fe1b096e6bf3646c7344c38786ed688f464","crates\u002Fenowx-core\u002Fskills\u002Fui-stack-react",21,"https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli","https:\u002F\u002Fenowx.ai","https:\u002F\u002Fgithub.com\u002Fenowdev","Pretending to be a Developer","2026-10-06 02:05:35",{"source":8,"skillName":5,"commands":62,"prompt":72,"zipAvailable":73},[63,66,69],{"tool":64,"command":65},"npx","npx skills add enowdev\u002Fenowxcli --skill ui-stack-react",{"tool":67,"command":68},"bunx","bunx skills add enowdev\u002Fenowxcli --skill ui-stack-react",{"tool":70,"command":71},"pnpm","pnpm dlx skills add enowdev\u002Fenowxcli --skill ui-stack-react","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-react\u002FSKILL.md",true,[],[76,89,114,127,154,175],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":85,"publishedAt":47},"01M6D2HQYSQD9N7MTNDT4A7CBM","demo-skill","Project-scope demo skill for tests.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fworkspace\u002F.agents\u002Fskills\u002Fdemo-skill\u002FSKILL.md",[82],{"categoryNo":83,"categoryName":84,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[86,87,88,34,31],"demo skill","demo","tests",{"skillNo":90,"name":91,"nameZh":6,"description":92,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":93,"categories":94,"keywords":103,"publishedAt":47},"01M6D2HM0GCPM0TM2HPG9HHR2R","writing","Writing words people read: the specific thing instead of claims, words to drop, sentences, interface copy (buttons, labels, errors, empty states, confirmations), page copy, notifications and emails, alt text and link text, numbers and dates, words that translate well, documentation and voice. Read before writing a page's copy, an interface's text or a document.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fwriting\u002FSKILL.md",[95,98],{"categoryNo":96,"categoryName":97,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":99,"categoryName":100,"taxonomyType":101,"taxonomyTypeDesc":102},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[91,104,105,106,107,108,31,109,110,111,112,113],"text","emails","empty","drop","document","page","voice","notifications","documentation","translate",{"skillNo":115,"name":29,"nameZh":6,"description":116,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":117,"categories":118,"keywords":122,"publishedAt":47},"01M6D2HG07YS49NM196Q0R3AZ8","Designing or restyling an interface: direction, layout, type, colour, icons, components, states, responsive behaviour. Read before building a page, a screen or a set of components.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui\u002FSKILL.md",[119],{"categoryNo":120,"categoryName":121,"taxonomyType":101,"taxonomyTypeDesc":102},"01JBSEEDCAT000000000000032","设计师",[29,123,32,124,125,109,33,31,126],"icons","SET","responsive","before building",{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":139,"publishedAt":47},"01M6D2HB59HZHWG1RYZDV4SJ91","ui-themes","Light and dark themes, and colour that holds in both: tokens with a value per theme, and what each kind of content needs to stay visible (icons, SVG drawings and line art, logos, photos, screenshots, charts, code, maps, borders, shadows, focus, form controls), switching without a flash, and checking both. Read before building a second theme or a theme toggle, and before any drawing, logo or chart on a page that has two themes.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-themes\u002FSKILL.md",[133,136],{"categoryNo":134,"categoryName":135,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":137,"categoryName":138,"taxonomyType":101,"taxonomyTypeDesc":102},"01JBSEEDCAT000000000000030","开发工程师",[29,140,141,142,143,144,145,146,147,31,126,148,109,149,150,151,123,152,153],"ui themes","switching","photos","tokens","drawing","focus","maps","Art","flash","code","Charts","content","screenshots","logo",{"skillNo":155,"name":156,"nameZh":6,"description":157,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":158,"categories":159,"keywords":163,"publishedAt":47},"01M6D2H5R5KRAE524C8QX0T7Q0","ui-structure","Structure before styling: the blueprint written before any interface code (the screen's job, the regions its kind of page needs, their order and why, the layout skeleton in numbers, the components and their states), the anatomy and order of a homepage, a dashboard, a list, a detail page, settings, a form, sign-in, pricing, docs, an article and a checkout, and how to turn the blueprint into code (tokens, layout primitives, components, sections, page). Read first, before building or rebuilding any page or screen.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-structure\u002FSKILL.md",[160,161,162],{"categoryNo":96,"categoryName":97,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":137,"categoryName":138,"taxonomyType":101,"taxonomyTypeDesc":102},{"categoryNo":99,"categoryName":100,"taxonomyType":101,"taxonomyTypeDesc":102},[164,29,165,32,166,167,149,168,169,170,33,143,171,39,172,31,126,109,173,174],"structure","ui structure","pricing","dashboard","Blueprint","article","anatomy","styling","list","why","how",{"skillNo":176,"name":177,"nameZh":6,"description":178,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":179,"categories":180,"keywords":184,"publishedAt":47},"01M6D2H0ZZBJG77MBGPZHA6MWD","ui-stack-vue","Building interfaces in Vue 3 and Nuxt: project layout, script setup with typed defineProps, defineEmits and defineModel, attribute fallthrough, slots and scoped slots, template refs, ref, reactive and computed, watch versus watchEffect, composables and VueUse, provide and inject, Pinia, keyed v-for, transitions, Nuxt 4 (app directory, pages, layouts, useFetch and useAsyncData keys, $fetch in handlers, server routes, runtime config, useSeoMeta, NuxtImg, middleware), VeeValidate forms, Reka UI and Nuxt UI, testing, vue-tsc, and the traps. Read when the project uses Vue or Nuxt.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-vue\u002FSKILL.md",[181,182,183],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":120,"categoryName":121,"taxonomyType":101,"taxonomyTypeDesc":102},[29,185,27,186,36,187,188,189,33,34,190,191,31,192,193,194,45,195,196,41],"vue","ui stack vue","pages","watch","runtime","transitions","fetch","pinia","nuxt","template","config","directory"]