Claude Code with React: A Frontend Developer's Workflow
Why React projects need project-specific Claude Code configuration
Claude Code understands React. It knows useState, useEffect, useCallback, React.memo, and every hook in the standard library. It knows Vite, Vitest, Jest, React Testing Library, and the component folder conventions used by most teams.
The problem is that this knowledge is generic. Without configuration, Claude Code generates components that run but do not fit. They use the wrong import aliases, ignore your custom hook patterns, skip your test conventions, and produce prop interfaces that do not match your existing types. You end up reviewing and retrofitting instead of shipping.
The solution is a project-specific CLAUDE.md that encodes your decisions once and applies them every session. This guide builds that configuration layer, then covers the component authoring loop, hook extraction workflow, state management patterns, and testing setup that make React development with Claude Code genuinely fast. If you are new to Claude Code entirely, the Claude Code setup guide covers installation and authentication before any of this applies.
The React CLAUDE.md
A good React CLAUDE.md answers five questions: how is the project structured, how are components authored, how are hooks named, how is state managed, and how are tests run? Answer these once and Claude Code applies them consistently for the life of the project.
# React project rules
## Project structure
- Components in src/components/{ComponentName}/index.tsx
- Colocate styles: {ComponentName}.module.css next to index.tsx
- Custom hooks in src/hooks/use{Name}.ts
- Types in src/types/{domain}.ts (no inline type exports from component files)
- Path alias: @/ maps to src/
## Component conventions
- Functional components only. No class components.
- Named exports only. No default exports on components.
- Props interface defined above the component: interface {Name}Props {}
- Component file = one component. Split when a second component appears.
- Prefer composition over prop drilling beyond two levels.
## Hooks
- All data fetching via TanStack Query (useQuery / useMutation)
- Custom hooks extract logic from components; they do not render
- Hook files export one hook each
- Side effects in useEffect must list all dependencies; no suppression comments
## State management
- Local UI state: useState
- Cross-component state: Zustand stores in src/stores/{name}.store.ts
- Server state: TanStack Query only. No manual fetch + useState for remote data.
- Avoid useContext for anything that changes frequently
## Testing
- Framework: Vitest + React Testing Library
- Run: `npx vitest run` for the full suite
- Watch mode: `npx vitest`
- Coverage: `npx vitest run --coverage`
- Test files colocated: {ComponentName}.test.tsx next to index.tsx
- Do not test implementation details. Test user-visible behavior.
## Hard rules
- No useEffect for data fetching. Use TanStack Query.
- No inline styles. CSS Modules only.
- All images need alt text. Decorative images: alt=""
- TypeScript strict mode is on. No any types.
This file loads every session. Claude reads it before authoring a single component. The output fits your project instead of a generic React template.
The component authoring loop
The single most productive Claude Code workflow for React is a tight authoring loop: requirements to component to tests, all in one pass.
The prompt structure that works consistently:
"Create a ProductCard component. It receives a product: Product prop (type in src/types/product.ts). It displays the product image, name, price formatted with formatCurrency from src/utils/format.ts, and an add-to-cart button that calls an onAddToCart(id: string) callback. Follow the project component conventions."
With a complete CLAUDE.md, Claude Code will:
- Create
src/components/ProductCard/index.tsxwith a named export - Define
ProductCardPropsabove the component - Import
Productfromsrc/types/product.tsrather than inlining the type - Import
formatCurrencyfrom the correct utility path - Create
ProductCard.module.cssfor styles - Create
ProductCard.test.tsxthat tests visible behavior, not implementation details
What you get without the CLAUDE.md is a self-contained file with inlined types, hardcoded styles, and a default export. Both compile. Only one fits your codebase.
Iterating on a component
The revision loop works best when you are specific about what needs to change. "Make it look better" produces inconsistent results. "The add-to-cart button should be disabled when product.stock === 0 and show 'Out of stock' as its label" produces exactly what you asked for.
For visual changes that involve responsive behavior or breakpoints, describe the target state at each breakpoint rather than the CSS property you have in mind. Claude Code maps desired behavior to correct CSS reliably. It maps "make it bigger on mobile" to whatever it guesses you mean less reliably.
Custom hook extraction
React components accumulate logic. A component that starts as 60 lines becomes 200 lines as features are added. The right response is hook extraction, and Claude Code handles it well when you frame the task correctly.
The pattern:
"This UserDashboard component has data-fetching logic, permission checking, and local filter state all mixed together. Extract the data-fetching and permission checking into a useUserDashboard hook. Keep only rendering logic in the component. The hook should return the data, permission flags, and the filter state with its setter."
Claude Code reads the component, identifies the logical boundaries, moves the logic into a hook with a clean interface, and updates the component to use it. The result is testable in isolation.
What Claude Code does well here is recognizing implicit dependencies. If your data fetching relies on a permission check before firing, Claude maintains that relationship in the hook rather than extracting them as two independent things. This is the kind of structural reasoning that separates Claude Code from a basic code-generation tool.
Documenting hook contracts
After extraction, add a brief contract comment to each hook. Claude Code can write these from the hook implementation:
"Add a JSDoc comment to useUserDashboard describing what it returns, what queries it fires, and what side effects it produces."
This pays dividends when Claude Code works on components that consume the hook in a future session. The comment gives it the interface without needing to read the hook implementation.
State management patterns
The most common mistake React developers make with Claude Code is not specifying where state lives. Without guidance, Claude defaults to useState for everything. On small projects that is fine. On projects with cross-component state requirements, it produces component trees that prop-drill five levels deep.
Your CLAUDE.md handles this by assigning state to the right layer: useState for local UI state, TanStack Query for server state, Zustand for shared client state. Claude Code follows this routing once it is explicit.
For Zustand stores, the pattern that produces consistent output:
"Create a cartStore in src/stores/cart.store.ts. It should hold an array of CartItem (type in src/types/cart.ts). Expose actions: addItem(item: CartItem), removeItem(id: string), clearCart(). The addItem action should increment quantity if the item already exists rather than adding a duplicate."
Claude Code generates a typed Zustand store with the correct create signature, proper TypeScript inference, and actions that match the described behavior. It will not add persistence middleware or devtools unless you ask, which is correct behavior for a store that does not need them.
When to reach for context
React Context is not state management. It is dependency injection. Claude Code sometimes reaches for Context when Zustand would be more appropriate, particularly for state that changes on user interaction.
Add this to your CLAUDE.md if your project has seen this pattern appear:
- useContext is for static configuration (theme, locale, feature flags)
- Zustand stores handle any state that updates on user interaction
- Do not create context for data that changes more than once per page load
Explicit rules here cost one line each and save a full refactor later.
Testing React components with Vitest
The testing workflow that produces the most value runs tests inside Claude Code's loop rather than after it. The component is not finished until its tests pass.
## After every component or hook change
1. Run `npx vitest run src/components/{ComponentName}`
2. If tests fail, fix the failure before considering the task done
3. If coverage drops below 80% on the changed files, add tests
4. Never add `vi.mock` for internal modules without explaining why
With these rules in CLAUDE.md, Claude Code runs tests automatically after each change. Failures surface during the task, not during your review.
Writing tests Claude can maintain
React Testing Library tests written against the DOM rather than component internals are easier for Claude Code to maintain across refactors. When the component's implementation changes but the user-visible behavior stays the same, tests written against screen.getByRole and screen.getByText continue to pass. Tests written against internal state or component methods do not.
Two patterns that work well in practice:
// Good: tests visible behavior
test('add to cart button is disabled when stock is 0', () => {
render(<ProductCard product={outOfStockProduct} onAddToCart={vi.fn()} />);
expect(screen.getByRole('button', { name: /out of stock/i })).toBeDisabled();
});
// Good: tests user interaction
test('calls onAddToCart with product id when button is clicked', async () => {
const onAddToCart = vi.fn();
render(<ProductCard product={inStockProduct} onAddToCart={onAddToCart} />);
await userEvent.click(screen.getByRole('button', { name: /add to cart/i }));
expect(onAddToCart).toHaveBeenCalledWith(inStockProduct.id);
});
Ask Claude Code to write tests in this style explicitly when setting up a new component. It defaults to it when React Testing Library is specified in your CLAUDE.md, but an explicit instruction in the first test session locks the pattern for the project.
Debugging component issues
Debugging with Claude Code works best when you give it the error output, not a description of the error. Paste the full console output, the React DevTools component tree, or the failing test output. Claude reads this the way a developer does: starting from the root cause in the stack trace, not the symptom at the top.
For rendering bugs, the pattern that works:
"This component re-renders on every parent update even though its props have not changed. Here is the component: [paste]. Here is the profiler output: [paste]. Find the cause and fix it."
Claude Code identifies the missing React.memo wrapper, the unstable function prop reference that needs useCallback, or the context subscription that triggers unnecessary renders. It gives you the specific fix with the line to change, not a general lecture on memoization.
For useEffect dependency issues, paste the ESLint output from react-hooks/exhaustive-deps. Claude Code reads these warnings accurately and resolves them without introducing stale closure bugs, which is the failure mode when developers suppress the warning manually.
Check the Claude Code debugging guide for the full debugging workflow that applies across languages and frameworks.
Performance workflows
Performance optimization is one area where Claude Code needs clear scope. "Optimize this component" is underspecified. "Identify all unnecessary re-renders in this component tree and fix them without changing the user-visible behavior" is actionable.
The workflow for a performance audit:
- Paste the component and its children into the session.
- Ask Claude Code to list every place a new object or function is created on each render.
- Review the list before Claude makes changes.
- Approve the fix list, then let Claude implement.
This is the same plan-before-execute pattern that applies to multi-file editing. The planning pass surfaces the full scope. The execution pass is fast because the scope is already agreed.
For bundle size work, Claude Code responds well to: "Run npx vite-bundle-analyzer and identify the three largest modules in the bundle. For each one, tell me if it can be code-split or replaced with a smaller alternative."
What React developers get wrong first
Three mistakes appear consistently when React developers start using Claude Code.
Not specifying state management upfront. Without an explicit rule in CLAUDE.md, Claude will reach for useState for server data and useContext for shared state. Both work. Neither is what most modern React projects want. One line per state layer in CLAUDE.md prevents this.
Asking for a full feature in one prompt. "Build a product listing page with filters, sorting, pagination, and a cart drawer" produces a feature that compiles but does not fit the codebase. The better approach is the same layered prompting described above: data shape and types first, then hook, then component, then tests. Each layer is reviewable and correctable before the next one builds on top of it.
Letting Claude generate tests after the fact. Tests written after a component is complete tend to be implementation tests: they reflect what the code does rather than what the user sees. Tests written as part of the authoring loop are behavior tests. Ask for tests during the task, not as a follow-up.
Building a consistent React workflow
The configuration in this guide produces a setup where Claude Code generates components that fit your project, hooks that are extractable and testable, stores that follow your state management decisions, and tests that remain meaningful after refactors.
The foundation is the CLAUDE.md template above. Add it to your project root, run one component authoring session end-to-end, and adjust the rules based on what Claude generates. Most projects need three or four additional rules after the first session. After that, the output is consistent enough to review in seconds rather than minutes.
For the broader principles that apply across all Claude Code workflows, not just React, the Claude Code best practices guide covers the mental models and session management that make the difference between a slow and a fast development loop. For TypeScript-specific configuration that pairs with the React setup above, see the Claude Code TypeScript workflow.
More like this
Ready to upgrade your Claude Code setup?
Get Claudify