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.
What these React recipes give you
Section titled “What these React recipes give you”- A version-pin block for
CLAUDE.md,AGENTS.mdor 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 writes | Status on 2026-09-26 | Write instead |
|---|---|---|
useReactTable(options) | Not exported by @tanstack/react-table 9.2.4; only useLegacyTable from /legacy, marked deprecated | useTable({ ...options, features }) with tableFeatures() |
cacheTime in a query | Not in @tanstack/query-core v5 types | gcTime |
z.string().email() | @deprecated Use z.email() instead in Zod 4.6.5 | z.email() |
import { ReactComponent } from './logo.svg' | Create React App only | import Logo from './logo.svg?react' with vite-plugin-svgr 5 |
process.env.REACT_APP_API_URL | Create React App only | import.meta.env.VITE_API_URL |
Loading and error flags in useState around fetch | Works, but duplicates what the cache does | a 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:
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.
Put the rule in AGENTS.md, which Codex loads at the start of a session. In CI, codex exec runs the prompt headless; run npm run verify as the next step of the CI job so the job fails when the gate does, whatever the agent reported.
Put the rule in a project rule. Cursor Hooks can run a script at points in the agent loop, so the gate does not depend on the agent remembering the rule. Bugbot then reviews the pull request as a second opinion, not as the gate.
Turn a Figma frame into a typed component
Section titled “Turn a Figma frame into a typed component”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.
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/mcpcodex mcp add figma --url https://mcp.figma.com/mcpcodex mcp login figmaOr install Figma’s plugin, which bundles the server with its design-to-code skills:
codex plugin add figma@openai-curatedType /add-plugin figma in the agent chat (from Figma’s README), or add the server to .cursor/mcp.json:
{ "mcpServers": { "figma": { "url": "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.
Build a data table with TanStack Table v9
Section titled “Build a data table with TanStack Table v9”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.
Move server state to TanStack Query v5
Section titled “Move server state to TanStack Query v5”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.
Add optimistic UI without losing data
Section titled “Add optimistic UI without losing data”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:
claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latestcodex mcp add chrome-devtools -- npx chrome-devtools-mcp@latest{ "mcpServers": { "chrome-devtools": { "command": "npx", "args": ["-y", "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.
Migrate from Create React App to Vite
Section titled “Migrate from Create React App to Vite”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:
npx skills add vercel-labs/agent-skills --skill vercel-react-best-practicesnpx skills add vercel-labs/agent-skills --skill web-design-guidelinesvercel-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”| Gate | Catches | Runs where | Signs off |
|---|---|---|---|
tsc --noEmit with strict | Wrong props, lost generics, stale library APIs that no longer type-check | agent loop, CI | automatic |
eslint-plugin-react-hooks 7 with exhaustive-deps at error | Stale closures, conditional hooks, Rules of React violations | agent loop, CI | automatic |
| Vitest + Testing Library + MSW | Behaviour: what the user sees after each action, including error paths | agent loop, CI | automatic; the author reviews test names, not bodies |
| Characterization tests, unchanged across the refactor | Behaviour drift in refactors | agent loop, CI | author checks the test diff is empty |
Playwright + @axe-core/playwright | Missing labels, focus traps, contrast, roles | CI | automatic; an accessibility owner reviews new exceptions |
| Storybook screenshot next to Figma | Visual drift from the design | pull request | designer |
| Performance trace before/after | Regressions in the interaction you changed | agent loop with Chrome DevTools MCP | author; 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.
When agent-written React code breaks
Section titled “When agent-written React code breaks”- The agent writes an API the installed version does not have.
useReactTable,cacheTimeandz.string().email()are the common ones. Recovery: add the version-pin block, point the agent at the library’s bundled skill or its.d.tsfiles innode_modules, and lettscreject the rest. - Stale closures. A
useCallbackoruseEffectreads state its dependency array does not list, so it acts on old values. Recovery:exhaustive-depsaterror; never let the agent silence it with aneslint-disablecomment. 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.mockthe module under test. - Blind memoization.
memoon 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 anyat library boundaries. Generic tables and form resolvers lose type inference, and the agent casts. Recovery: treatas anyas a failed gate (@typescript-eslint/no-explicit-anyaterror) and ask for the generic signature instead.- Mocks leaking between tests. A test passes alone and fails in the suite. Recovery: set
restoreMocks: truein the Vitest config, and reset MSW handlers inafterEach. - 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.
Where to go next with React and agents
Section titled “Where to go next with React and agents”- Frontend recipes overview for the other frameworks in this section
- Next.js recipes for Server Components, the App Router and caching, which this page leaves to its neighbour
- Unit testing with agents and accessibility testing for the gates behind
verify - Playwright MCP and agent-browser for letting the agent check the running app
- React Native development patterns when the same team ships mobile