Modern React development promises speed and simplicity, but hidden performance traps lurk beneath the surface. When an AI assistant crafts your components, it often defaults to verbose configurations, misuse of React hooks, and unoptimized state management—practices that bloat your codebase and slow down rendering. The solution isn’t to avoid AI tools but to guide them with clear, structured rules that enforce modern best practices.
Why React Code Generated by AI Needs Guardrails
React’s declarative nature masks performance pitfalls that only surface under scrutiny. A component may render flawlessly during development, yet profiling reveals that a parent re-renders the entire tree on every keystroke. Or a useEffect fetches data twice in development because a dependency was removed to “fix” a warning, not because the logic demanded it. Context providers, intended to simplify prop drilling, can trigger re-renders across 40 sibling components whenever a token refreshes.
AI assistants like Cursor and Claude Code are trained on vast repositories of React code—but much of that code is outdated. A significant portion uses class components or pre-Hooks patterns. Even modern examples often overuse useCallback, useMemo, and useEffect without measuring their impact. Ask an AI to build “a form that loads and edits a user,” and it might return a 250-line component with five useState calls, a useEffect missing an AbortController, props typed as any, and a context provider three layers up—code that works but isn’t production-ready.
The antidote? .cursorrules, a single file that defines idiomatic, modern React development standards for your AI assistant.
How Cursor Rules Shape AI-Generated React Code
Cursor reads project rules from two locations: .cursorrules at the root of your repository or modular rule files in .cursor/rules/*.mdc. For React projects, modular rules are recommended—especially in monorepos where a Next.js app and a Vite SPA share code. This separation prevents Next.js server component conventions from clashing with client-side Vite patterns.
Each rule file includes frontmatter to control activation. For example:
---
globs:
- "**/*.tsx"
- "**/*.jsx"
with:
alwaysApply: false
---This ensures rules apply only to React files, not test or documentation files.
Recommended rule modules for React:
react-core.mdc– composition, props, and file structure
react-hooks.mdc– disciplineduseEffect,useMemo, anduseCallbackusage
react-state.mdc– managing local, global, and server state
react-perf.mdc– memoization, rendering boundaries, and key strategies
react-testing.mdc– behavior-first testing with React Testing Library
These rules act as guardrails, preventing AI assistants from generating code that violates modern React principles.
Rule 1: Favor Composition Over Configuration in Components
One of the most common anti-patterns in AI-generated React is the “boolean explosion.” An AI might interpret repeated usage of a Card component across your app and generalize it with numerous optional props: hasHeader, iconLeft, iconRight, variant, size, and an actions array. Over time, this component can swell to 30 props, with four mutually exclusive flags and brittle logic that breaks every time a consumer changes.
The modern React approach: use composition instead of configuration. Components should accept children and named slots (header, footer, actions) rather than a growing list of boolean toggles. Boolean props should only control genuine state—like disabled, loading, or selected—not whether a sub-element renders.
For polymorphic wrappers, use the as prop or a render-prop pattern. Avoid parallel props like linkTo and buttonHref that duplicate functionality.
Before: Boolean overload
interface CardProps {
title: string;
hasHeader?: boolean;
hasFooter?: boolean;
iconLeft?: 'user' | 'cart' | 'star';
actions?: Array<{ label: string; onClick: () => void; variant: string }>;
variant?: 'default' | 'bordered' | 'elevated';
collapsible?: boolean;
}Every new consumer adds another flag. The component becomes harder to maintain and test.
After: Composition with slots
interface CardProps {
header?: React.ReactNode;
footer?: React.ReactNode;
children: React.ReactNode;
}
function Card({ header, footer, children }: CardProps) {
return (
<section className="rounded-lg border bg-white shadow-sm">
{header && <header className="border-b p-4">{header}</header>}
{children}
{footer && <footer className="border-t p-4">{footer}</footer>}
</section>
);
}<Card header={<h3>Profile</h3>} footer={<SaveButton />}>
<UserDetails />
</Card>This version is ten lines, never needs to change, and encourages clean, maintainable UI patterns.
Rule 2: Enforce Hook Discipline to Avoid Silent Crashes
AI-generated components often misuse React hooks by calling them conditionally, scattering state across multiple useState calls, or wrapping them in early returns. These violations break the Rules of Hooks and can cause runtime errors when component lifecycles change.
The rule: All hooks must be called unconditionally at the top of the function, never inside if/else, loops, try/catch, or after early returns. Enforce this with eslint-plugin-react-hooks set to error level.
Group related state into a single useReducer instead of multiple useState calls. For example, manage loading, data, and error together when fetching data. If a combination of hooks is reused in two or more components, extract it into a custom hook that returns a stable object or a clean tuple.
Never use useEffect to compute derived state—calculate it inline or via useMemo. Derived values belong in the render phase, not side effects.
Before: Misused hooks and conditional logic
function UserCard({ userId }: { userId: string | null }) {
if (!userId) return null;
const [user, setUser] = useState(null);
const [fullName, setFullName] = useState('');
const [initials, setInitials] = useState('');
useEffect(() => {
if (user) {
setFullName(`${user.first} ${user.last}`);
setInitials(`${user.first[0]}${user.last[0]}`);
}
}, [user]);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
return `${fullName} — ${initials}`;
}This breaks hook order on re-renders when userId toggles, causing a React crash. The derived state (fullName, initials) should never be stored in state.
After: Clean hooks and derived state
function useUser(userId: string | null) {
const [state, dispatch] = useReducer(userReducer, {
status: 'idle',
user: null,
});
useEffect(() => {
if (!userId) return;
let ignore = false;
fetchUser(userId).then((user) => {
if (!ignore) dispatch({ type: 'SET_USER', payload: user });
});
return () => { ignore = true; };
}, [userId]);
return state;
}
function UserCard({ userId }: { userId: string | null }) {
const { user } = useUser(userId);
const fullName = user ? `${user.first} ${user.last}` : '';
const initials = user ? `${user.first[0]}${user.last[0]}` : '';
return `${fullName} — ${initials}`;
}This version uses a custom hook to manage state and avoids conditional hook calls. Derived values are computed during render, ensuring stability and correctness.
AI assistants can write React code quickly, but without guardrails, that code often becomes bloated, brittle, and inefficient. By defining and enforcing modern React patterns using .cursorrules, you ensure every line of code your AI generates aligns with performance, maintainability, and best practices. Start with the composition and hook discipline rules, then expand into state management and performance optimizations. The result isn’t just cleaner code—it’s a foundation for scalable, future-proof React applications.
AI summary
Learn how to use Cursor Rules to guide AI assistants and enforce modern React patterns, reducing component bloat and improving performance without manual refactoring.