Skip to main content

best-practices-react

React, Next.js, React Native, and web design best practices from Vercel Engineering. Use when writing, reviewing, or refactoring React/Next.js/React Native components, optimizing performance, auditing UI accessibility, or designing component APIs. Triggers: "review react", "optimize component", "check accessibility", "react best practices", "component architecture", "react native performance".

Ir para a instalação

Informações da origem

Repositório
grahama1970/agent-stack-public
Última atividade na origem
24 de setembro de 2026 às 15:51
Idioma detectado do SKILL.md
inglês
Estrelas
0
Forks
0

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
100 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
best-practices-react
description
React, Next.js, React Native, and web design best practices from Vercel Engineering. Use when writing, reviewing, or refactoring React/Next.js/React Native components, optimizing performance, auditing UI accessibility, or designing component APIs. Triggers: "review react", "optimize component", "check accessibility", "react best practices", "component architecture", "react native performance".
triggers
["best practices react","react review","react component","next.js","react native","component architecture"]
license
MIT
metadata
{"author":"vercel","version":"1.0.0","language":"typescript"}
provides
["best-practices-react"]
composes
["task-monitor","agentic-evals"]
disciplines
["engineering-standards","ui-design-engineering"]
# React Best Practices Collection Enterprise-grade React/Next.js/React Native rules from Vercel Engineering. 100+ rules across 4 sub-skills with impact-prioritized categories. ## Sub-Skills | Sub-Skill | Rules | Focus | |-----------|-------|-------| | `react-best-practices` | 40+ | React/Next.js performance optimization | | `web-design-guidelines` | 100+ | Accessibility, forms, animation, dark mode, i18n | | `react-native-skills` | 16 | Mobile performance, animations, platform APIs | | `composition-patterns` | 8+ | Component architecture, prop design, compound components | ## When to Use - **react-best-practices**: Writing React components, Next.js pages, data fetching, bundle optimization - **web-design-guidelines**: UI review, accessibility audit, design compliance, UX check - **react-native-skills**: React Native/Expo apps, mobile performance, native modules - **composition-patterns**: Refactoring boolean prop proliferation, building component libraries ## Quick Reference ### Critical Impact — NON-NEGOTIABLE Every interactive element gets **4 things at write time** — no exceptions, no retrofitting: 1. **`data-qid`** — stable CSS selector. Format: `component:element:qualifier` (colon-separated). Used by test manifests, CDP automation, and `verify-data-qid.py` enforcement. 2. **`data-qs-action`** — QuerySpec action ID for agent/voice execution. Format: `COMPONENT_ACTION` (uppercase, underscore). An agent resolves NL intent → action ID → `document.querySelector('[data-qs-action="APPROVE_ENTRY"]').click()` — zero latency, no database lookup, works offline. 3. **`title`** — human-readable label. Required for MIL-STD-1472H compliance, screen readers, tooltips. 4. **`useRegisterAction`** — registers the action to ArangoDB `app_actions` collection for training data, analytics, and cross-app action discovery. Components without all 4 are **not shippable**. `verify-data-qid.py` enforces `data-qid` coverage in CI. **Write-time checklist:** ```tsx <button data-qid="quarantine:action:approve" data-qs-action="QUARANTINE_APPROVE" title="Approve selected entry" onClick={() => handleAction(id, 'approve')} > Approve </button> // In component body (registers to ArangoDB app_actions for training flywheel): useRegisterAction('quarantine:action:approve', { app: 'datalake-explorer', action: 'QUARANTINE_APPROVE', label: 'Approve', description: 'Approve quarantined entry and remove from queue', }) ``` **Why both `data-qs-action` AND `useRegisterAction`?** - `data-qs-action` = runtime DOM introspection (agent clicks the element, zero latency) - `useRegisterAction` = ArangoDB registry (training data, cross-app discovery, analytics) - They use the SAME action ID (`QUARANTINE_APPROVE`) so intent → action → DOM click is one straight line **Enforcement**: `verify-data-qid.py` MUST run in CI and `/plan` DoD for any UX task. Exit 1 = not shippable. `/review-plan` MUST FAIL any UX plan without it. ### File size — capped by contents, not one number A file with logic (components, hooks, helpers) may not exceed **400 lines**. A file of pure data (style maps, token tables, fixtures) may not exceed **800**. Do not reuse a Python line limit here. JSX is verbally bulky — one element with `data-qid`, `data-qs-action`, `title`, `onClick` and `style` costs 6–8 lines — so 800 lines of TSX holds far less logic and far more branching than 800 of Python. The distinction matters more than either number: a 795-line style map is reviewable by scanning and has no control flow; a 500-line component means branching and state, which is the blast-radius problem this skill already describes. A ceiling only enforces modularity if it fires — measured over a 99-file React surface, 800 flagged 2 files, 400 flagged the ones worth splitting. ```bash python3 scripts/verify-file-size.py src/ # exit 1 on violation ``` Data files are **detected, not declared**, so the lower ceiling cannot be dodged by renaming. Existing violations go in `.file-size-allowlist` as `path: lines` — a debt marker that pins the current size, so an allowlisted file may not grow. Runs in CI and `/plan` DoD alongside `verify-data-qid.py`. - Eliminate request waterfalls (parallel fetching, `Promise.all`) - Bundle size (dynamic imports, tree shaking, barrel file avoidance) - Accessibility (aria-labels, semantic HTML, keyboard navigation) - FlashList over FlatList (React Native) ### High Impact - Server-side rendering and streaming - Focus states (`focus-visible` patterns) - Layout (flex patterns, safe areas) - Animation (Reanimated, `prefers-reduced-motion`) ### Medium Impact - Re-render optimization (`useMemo`, `useCallback`, React Compiler) - Forms (autocomplete, validation, error handling) - Dark mode (`color-scheme`, `theme-color` meta) - State management (Zustand patterns) ## File Structure ``` best-practices-react/ ├── SKILL.md # This file ├── README.md # Detailed sub-skill descriptions ├── CLAUDE.md # Agent guidance (skill creation) ├── skills/ │ ├── react-best-practices/ # React/Next.js performance rules │ │ ├── SKILL.md │ │ └── rules/ # Atomic rule files │ ├── web-design-guidelines/ # UI/UX/a11y rules │ │ ├── SKILL.md │ │ └── rules/ │ ├── react-native-skills/ # React Native/Expo rules │ │ ├── SKILL.md │ │ └── rules/ │ ├── composition-patterns/ # Component architecture rules │ │ ├── SKILL.md │ │ └── rules/ │ └── claude.ai/ │ └── vercel-deploy-claimable/ # Vercel deployment skill └── packages/ └── react-best-practices-build/ # TypeScript build tooling ``` ## Common Mistakes ### WRONG: Testing UI mutations through a `vite preview` proxy ```bash # UI served by `vite preview` (built dist) with a proxied API. # GET polling updates the screen, so the UI "looks live"... curl http://127.0.0.1:4173/api/health # 200 (GET proxied) # ...but every user action silently does nothing: curl -X POST http://127.0.0.1:4173/api/event # dropped by the preview proxy ``` `vite preview`'s proxy does not forward POST/PUT/DELETE (vitejs/vite#13455); only `vite dev`'s proxy does. A SPA whose reads work but whose clicks/keys/forms do nothing is almost always this — not a handler bug. Hours were lost here debugging click wiring that was already correct. ### RIGHT: Serve mutation-testable UI same-origin, or use `vite dev` ```bash # Best: backend serves the built dist AND /api on one origin -> no proxy at all. # GET / -> index.html, GET /assets/* -> built files, /api/* -> handlers # Acceptable for local dev: vite DEV server (proxy forwards POST) EXPLAIN_PROJECT_API_URL=http://127.0.0.1:15174 npx vite dev --port 5174 # Prove POST actually reaches the backend before debugging any handler: curl -X POST http://127.0.0.1:5174/api/event -d '{...}' -w '%{http_code}' ``` **Why:** A production/interview UI must not depend on a proxy that drops writes. When a click "does nothing," first prove a raw POST reaches the backend through the SAME origin the browser uses; only then look at the React handler. ### WRONG: `role="button"` on a `<div>` or SVG `<g>` as the interactive element ```tsx <g role="button" data-qid="diagram:node:x" onClick={go}>...</g> // SVG group <div role="button" data-qid="diagram:node:x" onClick={go}>...</div> ``` SVG groups and role-only divs are unreliable click targets for CDP automation, native `element.click()`, and assistive tech, and they miss the four-attribute contract. A real user's click may land on a child (`<circle>`) or pass through. ### RIGHT: A real `<button type="button">` with the full contract ```tsx <button type="button" data-qid={`diagram:node:${nodeId}`} data-qs-action={`COCKPIT_DIAGRAM_STEP_${stepIndex}`} title={`Jump to step ${stepIndex + 1}`} onClick={go}>...</button> ``` **Why:** `<button>` fires `onClick` reliably for CDP, native `.click()`, keyboard (Enter/Space, no hand-rolled `onKeyDown`), and real mice. Any element a user clicks must be a real interactive element, not a styled `<div>`/`<g>`. ### WRONG: Driving a React handler with synthetic events or an off-screen click ```bash # synthetic MouseEvent does not reliably trigger React's delegated listener el.dispatchEvent(new MouseEvent('click', {bubbles:true})) // often no-op surf click "[data-qid=x]" # returns OK but the element is scrolled out of view -> hits nothing ``` ### RIGHT: Prove the harness, scroll into view, then click a trusted event ```bash # 1. Confirm a KNOWN-good control works (e.g. a keyboard shortcut) to prove dispatch is alive. # 2. scrollIntoView, THEN issue a trusted CDP click, THEN read the backend/state for the effect. surf js "document.querySelector('button[data-qs-action=X]').scrollIntoView({block:'center'})" surf click "button[data-qs-action=X]" # 3. Read back the EFFECT (revision bumped, state changed) — a click command returning "OK" is not proof. ``` **Why:** A `click` command reporting success is not proof the handler ran. CDP clicks need the element in the viewport; synthetic events often bypass React. Verify by reading the resulting state/revision, and isolate harness failures from code failures before editing the component. ### WRONG: `useRegisterAction` inside JSX, `.map()`, or function params ```tsx // BROKEN — hook inside .map() callback (violates Rules of Hooks) {items.map((item) => { useRegisterAction('list:item', { ... }) // ← CRASH or silent failure return <div>{item.name}</div> })} // BROKEN — hook inside function parameter destructuring function MetaItem({ useRegisterAction('meta:item', { ... }) // ← syntax error label, value, }: Props) { ... } ``` ### RIGHT: `useRegisterAction` at top of component function body ```tsx export default function QuarantineView({ entries }: Props) { // Hooks FIRST — before any other code useRegisterAction('quarantine:action:approve', { ... }) useRegisterAction('quarantine:action:reject', { ... }) const [selected, setSelected] = useState(null) // ... rest of component ``` **Why:** React hooks must be called at the top level of a function component. Mechanical instrumentation scripts that inject hooks by line number will place them inside JSX or callbacks. Always verify placement manually. ### WRONG: Import paths verified only by `tsc --noEmit` ```bash npx tsc --noEmit # exit 0 — "TypeScript clean!" # But Vite dev server shows: Failed to resolve import "../../../hooks/useRegisterAction" ``` ### RIGHT: Verify imports against the live Vite dev server ```bash # TSC and Vite resolve paths differently. Always check Vite: curl -s -o /dev/null -w "%{http_code}" "http://localhost:3002/src/components/MyComponent.tsx" # 200 = compiles. 500 = broken import. Do this for EVERY modified file. ``` **Why:** `moduleResolution: "bundler"` in tsconfig means TSC skips resolution for some imports. Vite resolves them at serve time and will 500 on wrong relative paths. A file can pass `tsc --noEmit` and still crash in the browser. ### WRONG: Test manifest generated from `grep` of TSX source files ```bash # Grepping source finds 124 data-qid values. But 91 of them are inside # modals, dropdowns, and subcomponents that only render after user interaction. grep -roh 'data-qid="[^"]*"' src/components/ | sort -u # ← 124 qids # Live DOM on page load has only 33. The other 91 don't exist yet. ``` ### RIGHT: Generate test manifest from the live DOM via CDP ```bash # Use /test-interactions generate, or query the live DOM directly: ./run.sh generate --url "http://localhost:3002/#my-view" --output manifest.json # Or via CDP JavaScript execution: # document.querySelectorAll('[data-qid]') on EACH tab/view state ``` **Why:** Components render conditionally. A `data-qid` in TSX source only exists in the DOM when that component is mounted. Test manifests must reflect what the user can actually see and click, not what exists in source code. ### WRONG: Rebuilding a chat/control well inside a large page monolith ```tsx // One 2,000-line route component owns the grid, data cards, alerts, voice UI, // chat well, prompt copy, timers, command registry, and distance-mode branches. function KioskDistanceView() { return ( <> <MetricGrid /> <aside>{/* chat well markup and state inline */}</aside> </> ) } ``` This makes a small chat revert dangerous. A request such as "put the orb back" can accidentally mutate grid layout, alert cards, distance-mode headers, or other unrelated UI because all concerns share one file and one style object. ### RIGHT: Extract volatile wells before non-trivial UX iteration ```tsx function KioskDistanceView() { return ( <> <MetricGrid /> <EmbryKioskChatWell sharedOrbState={sharedOrbState} commands={commands} onSelectPage={onSelectPage} /> </> ) } ``` **Why:** Chat wells, voice panels, drawers, artifact inspectors, and other high-churn control surfaces need their own component boundary before design iteration. This rule went unenforced for want of a number: one real route component reached **14,767 lines with 173 top-level declarations**, including a 6,280-line root, because nothing failed until someone read it. See the file-size ceilings above. Preserve the accepted component or restore it with `git show` / `git revert` instead of re-bespoking it inside the page. The parent route should compose the well and pass data/actions; it should not own the well's markup, prompt copy, timers, visual state machine, and command registry. If the human asks to revert a chat/control surface, first identify the last known-good component commit and make the revert path explicit before applying new edits. ### WRONG: Static `data-qid` on dynamic list items ```tsx // Every entry gets the same qid — selector matches multiple elements {entries.map(e => ( <div data-qid="quarantine:entry" onClick={() => select(e.id)}> {e.name} </div> ))} ``` ### RIGHT: Dynamic `data-qid` with stable identifier ```tsx {entries.map(e => ( <div data-qid={`quarantine:entry:${e.id}`} onClick={() => select(e.id)}> {e.name} </div> ))} ``` **Why:** Test manifests need unique selectors. If 18 entries share `data-qid="quarantine:entry"`, `querySelector` always hits the first one. Use the entity ID to make each qid unique. Note: dynamic qids change when data changes, so test manifests for list items must query the live DOM or use `:nth-child` fallbacks.
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub