React has a lot of history. Cursor knows class components, lifecycle methods, three different state management approaches, and both the old and new ways of doing everything. Without guidance, it'll mix them.
Here's the .cursorrules file that keeps a modern React project consistent.
The file
# React project rules
## Stack
React 18, TypeScript (strict), Vite, React Query for data, Zustand for UI state
## Component patterns
- Functional components only. No class components.
- No React.FC or React.FunctionComponent type annotations — use explicit prop types
- Props interface: ComponentNameProps (e.g., ButtonProps, UserCardProps)
- One component per file. Filename = component name (PascalCase).
- Default export for the component, named exports for types/utils from same file
## Hooks
- Custom hooks: use* prefix, live in hooks/
- No useEffect for data fetching — use React Query
- useEffect only for: subscriptions, DOM manipulation, non-React integrations
- Dependencies array: always complete — no eslint-disable comments
## State
- Server state: React Query (queries + mutations)
- Global UI state: Zustand (stores in stores/)
- Local component state: useState, useReducer
- Do not use Context for frequently-changing values (performance)
## Data fetching
- All API calls through React Query hooks
- Query keys as constants in queryKeys.ts
- Optimistic updates for mutations where UX requires it
## Styling
- CSS Modules. No inline styles except for dynamic values.
- Class names: camelCase in module, kebab-case in CSS
- No Tailwind unless already present in the file you're editing
## Performance
- No premature memoization — use memo/useCallback/useMemo only when profiling shows a need
- Lazy load routes and heavy components with React.lazy + Suspense
## TypeScript
- No 'any'. Use 'unknown' + type guard if needed.
- Discriminated unions for state that has multiple shapes
- No non-null assertions (!). Handle undefined explicitly.
## Rules
- Minimal footprint. Only modify files required for the task.
- If you notice other issues while working, note them in a comment but don't fix them.
- Never claim work is done without showing it renders without errors.
The decisions this encodes
No React.FC — It widens the children prop in older TypeScript. Using explicit prop types is cleaner.
No useEffect for data fetching — This is the most common React mistake in AI-generated code. The rule prevents it.
No premature memoization — AI defaults to wrapping things in memo and useCallback. This adds complexity without measured benefit in most cases.
Discriminated unions — AI often uses optional properties where discriminated unions are cleaner and more type-safe. The explicit call makes a difference.
Customizing for your stack
If you're using different libraries, update the state and data fetching sections. The structural rules (one component per file, functional only, no useEffect for fetching) apply regardless of your state management choices.
Generator for your React stack: builtbyzac.com/tools/cursorrules-generator.html.
Top comments (0)