Skip to content

React Development Recipes

React development recipes for AI agents are copy-paste prompts for production React 19 code in Claude Code, Codex or Cursor. Each pairs a prompt with the gate that proves the result: type check, React Hooks lint rules, Vitest with Testing Library, and a Playwright accessibility scan. Start by pinning library versions: agents default to APIs that no longer exist.

This page is for React developers and the tech leads who set their team’s agent conventions. You ask the agent for a data table and it writes useReactTable, which TanStack Table v9 no longer exports. You ask for a form and get z.string().email(), deprecated in Zod 4. The code looks idiomatic, the diff is 600 lines, and you cannot read all of it before Friday.

  • A version-pin block for CLAUDE.md, AGENTS.md or a Cursor rule that stops the agent from writing TanStack Table v8, TanStack Query v4, Zod 3 or Create React App code
  • One “definition of done” command the agent runs before it reports success, so you review a green gate instead of every line
  • Eight prompts for common React work: Figma to component, data tables, server state, optimistic UI, forms, class-to-hooks refactors, performance, and a Create React App to Vite migration
  • A table of which gate catches which class of bug, and who signs the change off
  • The failure modes of agent-written React and how to get out of each one

Pin the React stack in the agent’s context first

Section titled “Pin the React stack in the agent’s context first”

Agents learned React from years of React 16–18 code, TanStack Table v8 and Create React App tutorials. Without a pin they reproduce it. Put the versions, the banned APIs and the done command in the instructions your tool reads: CLAUDE.md for Claude Code, AGENTS.md for Codex, and a project rule for Cursor. Claude Code v2.1.277 and later on the latest release channel also reads AGENTS.md when a project has no CLAUDE.md.

## Frontend stack (React)
- React 19.3, TypeScript strict, Vite 8, Vitest 5 + Testing Library, Playwright for e2e.
- Server state: TanStack Query v5. Client state: Zustand 5. Forms: React Hook Form 7 + Zod 4
via @hookform/resolvers. Tables: TanStack Table v9 (`useTable` + `tableFeatures`).
- Never write: useReactTable, cacheTime, `import { ReactComponent } from './x.svg'`,
process.env.REACT_APP_*, z.string().email(), `as any`, useState+useEffect loading flags
for server data.
- Before you say a task is done, run `npm run verify` and paste the last 20 lines of output.
- Tests query by role and label, never by class name or test id unless no role exists.

The replacements below were checked against the published type definitions of the versions in the note above:

Agent writesStatus on 2026-09-26Write instead
useReactTable(options)Not exported by @tanstack/react-table 9.2.4; only useLegacyTable from /legacy, marked deprecateduseTable({ ...options, features }) with tableFeatures()
cacheTime in a queryNot in @tanstack/query-core v5 typesgcTime
z.string().email()@deprecated Use z.email() instead in Zod 4.6.5z.email()
import { ReactComponent } from './logo.svg'Create React App onlyimport Logo from './logo.svg?react' with vite-plugin-svgr 5
process.env.REACT_APP_API_URLCreate React App onlyimport.meta.env.VITE_API_URL
Loading and error flags in useState around fetchWorks, but duplicates what the cache doesa useQuery hook per endpoint

Give the agent one command that proves the change

Section titled “Give the agent one command that proves the change”

A recipe is only as good as the check that runs after it. Add one script that chains every gate, and make the agent run it before it reports done:

{
"scripts": {
"verify": "tsc --noEmit && eslint . --max-warnings 0 && vitest run && playwright test --grep @a11y"
}
}

Use the flat recommended config from eslint-plugin-react-hooks 7, and raise react-hooks/exhaustive-deps to error: stale closures are a common agent bug, and this rule catches them without a human reading the dependency array. In v7 the flat preset is reactHooks.configs.flat.recommended; the plugin:react-hooks/recommended string that agents often write is the legacy .eslintrc form:

eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';
export default defineConfig([
reactHooks.configs.flat.recommended,
{ rules: { 'react-hooks/exhaustive-deps': 'error' } },
]);

The @a11y Playwright tests run @axe-core/playwright against each page the change touched.

How you make the agent run verify differs by tool:

Put the rule in CLAUDE.md as in the block above. To enforce it rather than ask for it, add a Stop hook to .claude/settings.json. Exit code 2 blocks the stop and hands the gate’s output, sent to stderr, back to Claude:

{
"hooks": {
"Stop": [{ "hooks": [{ "type": "command",
"command": "jq -e '.stop_hook_active' >/dev/null && exit 0; npm run verify >&2 || exit 2" }] }]
}
}

The stop_hook_active check lets the stop through on the second attempt, so a failure Claude cannot fix ends the turn and reaches you instead of looping; Claude Code also caps stop-hook continuations at eight in a row. See hooks automation for a version that runs only on a dirty working tree. For a headless run in CI, claude -p takes the same prompt.

Scenario: you have a Figma frame for a pricing card and want a component that reuses your existing Button and Card instead of inventing new ones.

The Figma MCP server gives the agent the frame’s layout, variables and Code Connect mappings. Figma’s plugin bundles the remote server (https://mcp.figma.com/mcp) with its design-to-code skills. Check your seat before you plan a sprint on it: Starter plans, and View or Collab seats on paid plans, get up to 6 tool calls per month, while Dev and Full seats on Professional, Organization and Enterprise plans get per-minute limits. The details are in Figma MCP and design-to-code.

Terminal window
claude plugin install figma@claude-plugins-official
# or the server alone, then run /mcp in a session to log in:
claude mcp add --transport http figma https://mcp.figma.com/mcp

How you know it worked: the list of values with no token is empty or explained, verify is green, and a Storybook screenshot next to the Figma screenshot matches at your breakpoints. A designer compares those two images; nobody needs to read the Tailwind classes.

Scenario: an admin screen needs a sortable, filterable, paginated table of 10,000 users with row selection.

How you know it worked: grep -r "useReactTable\|useLegacyTable" src returns nothing, the four tests pass, and the axe scan finds no violations on the page. For server-side paging, point the agent at the with-tanstack-query skill in the same directory.

Scenario: 15 components each fetch in useEffect and keep their own loading and error flags. Data goes stale after an edit on another screen.

How you know it worked: grep -rn "useEffect" src/features/users shows no data fetching, the list-refresh test passes, and the component list matches the files in the diff. Mocking HTTP with MSW rather than mocking the hooks keeps the tests honest: the agent cannot make them pass by mocking the thing under test.

Scenario: toggling a todo waits 400 ms for the server, and users double-click.

Use React 19’s useOptimistic when the update comes from a form action and the state is local to one component. Use TanStack Query’s onMutate / onError / onSettled when the same data appears in several places through the query cache.

How you know it worked: the rollback test and the double-toggle test pass. Those two cases are where optimistic code loses data, and they are the only part of this diff worth a human glance.

Build forms with React Hook Form and Zod 4

Section titled “Build forms with React Hook Form and Zod 4”

Scenario: a settings form gets validation errors back from the server, and they land on the wrong field or nowhere at all.

How you know it worked: the four tests pass and the axe scan is clean. If the agent reaches for as any to satisfy the resolver types, reject the change: the version pin is wrong or the resolver is out of date.

Refactor a class component to hooks without changing behaviour

Section titled “Refactor a class component to hooks without changing behaviour”

Scenario: a 400-line class component with componentDidUpdate logic nobody fully understands, and no tests.

Write the tests before the refactor. A refactor that has only the agent’s word for “no behaviour change” is unverified. The method is in characterization tests.

How you know it worked: the test files are identical between the step 1 and step 2 commits (git diff HEAD~1 HEAD -- '*.test.tsx' is empty after both commits exist) and they pass on both. That diff is the one thing to check by hand.

Find the re-renders that actually cost time

Section titled “Find the re-renders that actually cost time”

Scenario: the product list takes seconds to respond to a filter change. The instinct, human or agent, is to wrap everything in memo, useMemo and useCallback.

Measure first. If the project already compiles with the React Compiler (babel-plugin-react-compiler 1.0.0 in package.json), most manual memoization is redundant, and the fix is usually a component the compiler skipped or an expensive computation during render. Chrome DevTools MCP gives the agent a performance trace of the real page:

Terminal window
claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest

The server sends usage statistics to Google by default; add --no-usage-statistics to opt out.

How you know it worked: the before/after table shows the interaction getting faster on the same trace, and the diff touches only the components the trace named. Watch the profiler rather than the code; see performance testing for making the number a CI budget.

Scenario: a Create React App project is still on react-scripts 5.0.1, the last stable release (April 2022), and its dev server takes minutes to start.

How you know it worked: grep -rn "REACT_APP_\|react-scripts" src package.json .env* returns nothing, and the passing-test list matches the baseline. The larger version of this job is in legacy modernization.

Add a React review skill for a second opinion

Section titled “Add a React review skill for a second opinion”

Two skills from vercel-labs/agent-skills review React code against written rules, and install into Claude Code, Codex and Cursor through the same CLI. The --skill value is the frontmatter name, not the folder name:

Terminal window
npx skills add vercel-labs/agent-skills --skill vercel-react-best-practices
npx skills add vercel-labs/agent-skills --skill web-design-guidelines

vercel-react-best-practices flags data-fetching waterfalls, bundle size and re-render problems; web-design-guidelines reports accessibility, focus and form issues as file:line findings. Use them as reviewers after verify is green, not as the gate. More options are on the best skills for frontend development.

How to verify agent-written React code without reading every line

Section titled “How to verify agent-written React code without reading every line”
GateCatchesRuns whereSigns off
tsc --noEmit with strictWrong props, lost generics, stale library APIs that no longer type-checkagent loop, CIautomatic
eslint-plugin-react-hooks 7 with exhaustive-deps at errorStale closures, conditional hooks, Rules of React violationsagent loop, CIautomatic
Vitest + Testing Library + MSWBehaviour: what the user sees after each action, including error pathsagent loop, CIautomatic; the author reviews test names, not bodies
Characterization tests, unchanged across the refactorBehaviour drift in refactorsagent loop, CIauthor checks the test diff is empty
Playwright + @axe-core/playwrightMissing labels, focus traps, contrast, rolesCIautomatic; an accessibility owner reviews new exceptions
Storybook screenshot next to FigmaVisual drift from the designpull requestdesigner
Performance trace before/afterRegressions in the interaction you changedagent loop with Chrome DevTools MCPauthor; a CI budget when it matters

A human reviews what the gates cannot: the acceptance criteria, the test names, and any diff to a test file. Write the acceptance criteria before the prompt; see acceptance criteria. Keep the agent from editing its own tests to make them pass; see protect the oracle.

  • The agent writes an API the installed version does not have. useReactTable, cacheTime and z.string().email() are the common ones. Recovery: add the version-pin block, point the agent at the library’s bundled skill or its .d.ts files in node_modules, and let tsc reject the rest.
  • Stale closures. A useCallback or useEffect reads state its dependency array does not list, so it acts on old values. Recovery: exhaustive-deps at error; never let the agent silence it with an eslint-disable comment. Ban that comment in review.
  • Tests that test the mock. The agent mocks the hook it wrote and asserts the mock returned what it was told to. Recovery: mock at the HTTP boundary with MSW and reject tests that vi.mock the module under test.
  • Blind memoization. memo on every component adds comparison cost and hides the real problem. Recovery: require a trace number for every memoization, as in the performance prompt; with the React Compiler on, remove manual memoization the trace does not justify.
  • as any at library boundaries. Generic tables and form resolvers lose type inference, and the agent casts. Recovery: treat as any as a failed gate (@typescript-eslint/no-explicit-any at error) and ask for the generic signature instead.
  • Mocks leaking between tests. A test passes alone and fails in the suite. Recovery: set restoreMocks: true in the Vitest config, and reset MSW handlers in afterEach.
  • Design drift from Figma. The agent hard-codes a hex or spacing value. Recovery: the prompt asks for a list of values with no token; the designer compares screenshots, not class names.