Cursor Rules for React: 6 Rules That Stop AI From…
    Neura MarketNeura Market/Cursor
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeek
    CoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    View All Directories
    OverviewRulesPromptsMCPsAgentsGamesBlogVideosGuidesCoursesCommunityExtensionsTrending
    CursorBlogCursor Rules for React: 6 Rules That Stop AI From Generating Bad Component Code
    Back to Blog
    Cursor Rules for React: 6 Rules That Stop AI From Generating Bad Component Code
    react

    Cursor Rules for React: 6 Rules That Stop AI From Generating Bad Component Code

    Olivia Craft April 18, 2026
    0 views

    Cursor Rules for React: 6 Rules That Stop AI From Generating Bad Component Code Cursor...

    Cursor Rules for React: 6 Rules That Stop AI From Generating Bad Component Code

    Cursor generates React code fast. The problem? It generates bad React code fast — anonymous inline handlers, inline styles scattered everywhere, 400-line god components, untyped props, missing dependency arrays, and .map() calls that explode on null data.

    You can fix this by adding targeted rules to your .cursorrules or .cursor/rules/*.mdc files. Here are 6 rules I use on every React project, with bad vs. good examples showing exactly what changes.


    Rule 1: No Anonymous Functions as Event Handlers

    Never use anonymous arrow functions directly in JSX event handlers.
    Always extract handlers into named functions declared before the return statement.
    

    Anonymous handlers make components harder to debug, impossible to test in isolation, and cause unnecessary re-renders.

    Without this rule, Cursor generates inline handlers constantly:

    // ❌ Bad: anonymous functions everywhere
    function ProductCard({ product, onAdd }) {
      return (
        <div>
          <h3>{product.name}</h3>
          <p>${product.price}</p>
          <button onClick={() => {
            if (product.stock > 0) {
              onAdd(product.id);
              toast.success(`Added ${product.name}`);
            } else {
              toast.error('Out of stock');
            }
          }}>
            Add to Cart
          </button>
          <button onClick={() => navigator.clipboard.writeText(product.url)}>
            Share
          </button>
        </div>
      );
    }
    

    Logic buried in JSX. Good luck setting a breakpoint on that.

    With this rule, Cursor extracts handlers:

    // ✅ Good: named handlers, testable and readable
    function ProductCard({ product, onAdd }) {
      function handleAddToCart() {
        if (product.stock > 0) {
          onAdd(product.id);
          toast.success(`Added ${product.name}`);
        } else {
          toast.error('Out of stock');
        }
      }
    
      function handleShare() {
        navigator.clipboard.writeText(product.url);
      }
    
      return (
        <div>
          <h3>{product.name}</h3>
          <p>${product.price}</p>
          <button onClick={handleAddToCart}>Add to Cart</button>
          <button onClick={handleShare}>Share</button>
        </div>
      );
    }
    

    Every handler has a name, a clear purpose, and you can find it in the debugger.


    Rule 2: No Inline Styles — Use CSS Modules or Tailwind

    Never use inline style objects in JSX. Use CSS Modules (*.module.css)
    or Tailwind CSS class names. If the project uses Tailwind, prefer
    utility classes. If it uses CSS Modules, import and use styles.
    

    Inline styles bypass your design system, can't use media queries, and create inconsistencies across components.

    Without this rule:

    // ❌ Bad: inline styles that drift from your design system
    function Alert({ message, type }) {
      return (
        <div style={{
          padding: '16px',
          borderRadius: '8px',
          backgroundColor: type === 'error' ? '#fee2e2' : '#dcfce7',
          color: type === 'error' ? '#991b1b' : '#166534',
          border: `1px solid ${type === 'error' ? '#fca5a5' : '#86efac'}`,
          fontSize: '14px',
          marginBottom: '12px',
        }}>
          <p style={{ margin: 0, fontWeight: 600 }}>{message}</p>
        </div>
      );
    }
    

    Magic hex values. No hover states. No responsive behavior. No dark mode.

    With this rule (Tailwind example):

    // ✅ Good: Tailwind classes, consistent with design system
    function Alert({ message, type }) {
      const styles = {
        error: 'bg-red-100 text-red-800 border-red-300',
        success: 'bg-green-100 text-green-800 border-green-300',
      };
    
      return (
        <div className={`p-4 rounded-lg border mb-3 ${styles[type]}`}>
          <p className="m-0 font-semibold text-sm">{message}</p>
        </div>
      );
    }
    

    Uses your design tokens. Supports dark mode, hover, responsive — all through the existing system.


    Rule 3: One Component Per File, Max 150 Lines

    One React component per file. The file name must match the component name.
    If a component exceeds 150 lines, split it into smaller components.
    Helper components used only by the parent go in a subfolder.
    

    Large files with multiple components become impossible to navigate and test.

    Without this rule:

    // ❌ Bad: UserDashboard.tsx — 380 lines, 4 components
    function UserAvatar({ user }) {
      // ...30 lines
    }
    
    function UserStats({ stats }) {
      // ...45 lines
    }
    
    function RecentActivity({ activities }) {
      // ...60 lines
    }
    
    export default function UserDashboard() {
      // ...200+ lines using all the above
    }
    

    Four components, one file. When UserAvatar breaks, you're scrolling past unrelated code to fix it.

    With this rule:

    // ✅ Good: one component per file
    components/
      UserDashboard/
        UserDashboard.tsx      // main component, <150 lines
        UserAvatar.tsx          // extracted, independently testable
        UserStats.tsx           // extracted, reusable elsewhere
        RecentActivity.tsx      // extracted, own loading states
        index.ts               // re-exports UserDashboard
    
    // UserDashboard.tsx
    import { UserAvatar } from './UserAvatar';
    import { UserStats } from './UserStats';
    import { RecentActivity } from './RecentActivity';
    
    export default function UserDashboard() {
      // clean, focused, <150 lines
    }
    

    Each component is testable, reviewable, and navigable on its own.


    Rule 4: Always Destructure Props With TypeScript Interfaces

    Always define a TypeScript interface for component props.
    Always destructure props in the function signature.
    Never use `props.x` access pattern. Never type props as `any`.
    

    Untyped or loosely-typed props are the #1 source of runtime errors in React apps.

    Without this rule:

    // ❌ Bad: no types, props bag passed around
    function InvoiceRow(props: any) {
      return (
        <tr>
          <td>{props.invoice.number}</td>
          <td>{props.invoice.client.name}</td>
          <td>${props.invoice.total.toFixed(2)}</td>
          <td>
            <button onClick={() => props.onPay(props.invoice.id)}>
              Pay
            </button>
          </td>
        </tr>
      );
    }
    

    What shape is invoice? What does onPay return? Your editor can't tell you.

    With this rule:

    // ✅ Good: typed interface, destructured props
    interface InvoiceRowProps {
      invoice: {
        id: string;
        number: string;
        client: { name: string };
        total: number;
      };
      onPay: (invoiceId: string) => void;
    }
    
    function InvoiceRow({ invoice, onPay }: InvoiceRowProps) {
      return (
        <tr>
          <td>{invoice.number}</td>
          <td>{invoice.client.name}</td>
          <td>${invoice.total.toFixed(2)}</td>
          <td>
            <button onClick={() => onPay(invoice.id)}>Pay</button>
          </td>
        </tr>
      );
    }
    

    Your editor autocompletes every field. Typos get caught at build time, not in production.


    Rule 5: useEffect Must Have Explicit Dependencies — Never Empty Unless Intentional

    Every useEffect must have an explicit dependencies array.
    Never leave the array empty unless the effect genuinely should
    only run on mount — and if so, add a comment explaining why.
    Never omit the array entirely.
    

    Missing or incorrect dependency arrays cause stale closures, infinite loops, and data that silently stops updating.

    Without this rule:

    // ❌ Bad: missing and incorrect dependency arrays
    function UserProfile({ userId }) {
      const [user, setUser] = useState(null);
      const [posts, setPosts] = useState([]);
    
      // Missing dependency: won't refetch when userId changes
      useEffect(() => {
        fetchUser(userId).then(setUser);
      }, []);
    
      // No array at all: runs on EVERY render
      useEffect(() => {
        fetchPosts(userId).then(setPosts);
      });
    
      // ...
    }
    

    Navigate from user 1 to user 2 and you're still seeing user 1's data. Classic stale closure bug.

    With this rule:

    // ✅ Good: explicit, correct dependencies
    function UserProfile({ userId }) {
      const [user, setUser] = useState(null);
      const [posts, setPosts] = useState([]);
    
      useEffect(() => {
        fetchUser(userId).then(setUser);
      }, [userId]); // refetches when userId changes
    
      useEffect(() => {
        fetchPosts(userId).then(setPosts);
      }, [userId]); // refetches when userId changes
    
      // Mount-only effect with explanation
      useEffect(() => {
        analytics.trackPageView('user-profile');
        // eslint-disable-next-line react-hooks/exhaustive-deps
      }, []); // intentionally mount-only: track page view once per navigation
    
      // ...
    }
    

    Every dependency is explicit. Empty arrays have comments justifying the decision.


    Rule 6: Always Handle Loading, Error, and Empty States

    Never render a bare .map() without checking for loading, error,
    and empty states first. Every data-driven component must handle:
    1. Loading state (skeleton or spinner)
    2. Error state (user-friendly message with retry option)
    3. Empty state (helpful message, not blank screen)
    4. Data state (the actual render)
    

    A bare .map() on undefined data throws a TypeError in production. Users see a white screen.

    Without this rule:

    // ❌ Bad: bare .map(), no state handling
    function OrderList({ customerId }) {
      const [orders, setOrders] = useState([]);
    
      useEffect(() => {
        fetch(`/api/orders?customer=${customerId}`)
          .then(res => res.json())
          .then(setOrders);
      }, [customerId]);
    
      return (
        <ul>
          {orders.map(order => (
            <li key={order.id}>
              Order #{order.number} — ${order.total}
            </li>
          ))}
        </ul>
      );
    }
    

    No loading indicator. No error handling. If the API returns null, the app crashes.

    With this rule:

    // ✅ Good: all states handled
    function OrderList({ customerId }) {
      const [orders, setOrders] = useState<Order[] | null>(null);
      const [error, setError] = useState<string | null>(null);
      const [isLoading, setIsLoading] = useState(true);
    
      useEffect(() => {
        setIsLoading(true);
        setError(null);
        fetch(`/api/orders?customer=${customerId}`)
          .then(res => {
            if (!res.ok) throw new Error('Failed to load orders');
            return res.json();
          })
          .then(setOrders)
          .catch(err => setError(err.message))
          .finally(() => setIsLoading(false));
      }, [customerId]);
    
      if (isLoading) return <OrderListSkeleton />;
      if (error) return <ErrorMessage message={error} onRetry={() => window.location.reload()} />;
      if (!orders || orders.length === 0) return <EmptyState message="No orders yet" />;
    
      return (
        <ul>
          {orders.map(order => (
            <li key={order.id}>
              Order #{order.number} — ${order.total}
            </li>
          ))}
        </ul>
      );
    }
    

    Every state has a UI. The user always knows what's happening.


    Copy-Paste Ready: All 6 Rules

    Here's a single block you can drop into your .cursorrules or .cursor/rules/react.mdc:

    # React Component Rules
    
    ## Event Handlers
    - Never use anonymous arrow functions in JSX event handlers
    - Extract all handlers into named functions before the return statement
    
    ## Styling
    - Never use inline style objects
    - Use CSS Modules or Tailwind utility classes
    
    ## File Structure
    - One React component per file
    - File name must match component name
    - Max 150 lines per component — split into subfolder if larger
    
    ## Props
    - Define TypeScript interfaces for all component props
    - Always destructure props in function signature
    - Never use `any` for props
    
    ## Effects
    - Every useEffect must have an explicit dependencies array
    - Empty arrays require a comment explaining why mount-only is correct
    - Never omit the dependencies array
    
    ## State Handling
    - Never render a bare .map() without null/undefined checks
    - Every data-driven component must handle: loading, error, empty, and data states
    - Show skeletons or spinners during loading
    - Show user-friendly error messages with retry options
    

    Want 50+ Production-Tested Rules?

    These 6 rules are a starting point. My Cursor Rules Pack v2 includes 50+ rules covering React, TypeScript, Next.js, Prisma, and testing — organized by framework and priority so Cursor applies them consistently.

    Stop fighting bad AI output. Give Cursor the rules it needs to write code your way.

    Tags

    reactcursortypescriptai

    Comments

    More Blog

    View all
    Cursor Automations in 2026: wire event-triggered coding agents to Slack, CI, and timerscursor

    Cursor Automations in 2026: wire event-triggered coding agents to Slack, CI, and timers

    Cursor Automations in 2026: wire event-triggered coding agents to Slack, CI, and...

    M
    Manu Shukla
    Index Everything, or Read Everything? The Dilemma of Feeding Specs to AI in Multi-Repo Developmentai

    Index Everything, or Read Everything? The Dilemma of Feeding Specs to AI in Multi-Repo Development

    The specs exist. The AI just can't see them. I've always been the type who builds hobby...

    S
    Shunya Shida
    Connect Claude Code, Cursor and Codex to Amazon Bedrock's new console (2026)amazonbedrock

    Connect Claude Code, Cursor and Codex to Amazon Bedrock's new console (2026)

    Connect Claude Code, Cursor and Codex to Amazon Bedrock's new console (2026) Summary. On 5...

    M
    Manu Shukla
    Spotting AI UI is too easyai

    Spotting AI UI is too easy

    There is a weird uncanny valley with LLM-generated UI right now. The code functions perfectly, but if...

    H
    Harish .s
    Seven ranking frameworks, one search page, zero translation tablesai

    Seven ranking frameworks, one search page, zero translation tables

    I went down a rabbit hole this morning reading the late-2025 Juejin AI roundups side by side, and the...

    N
    ninghonggang
    Zendesk MCP: Let Claude Handle Your Support Ticketsmcp

    Zendesk MCP: Let Claude Handle Your Support Tickets

    Install guide and config at curatedmcp.com Zendesk MCP: Let Claude Handle Your Support...

    C
    curatedmcp

    Stay up to date

    Get the latest Cursor prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for Cursor and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Guides

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.

    Neura Market

    Custom AI Systems & Services

    Our team of experienced AI builders will help build custom AI systems, workflows, and solutions for your business.

    Request custom work

    Ready-made automations for this

    Workflows from the Neura Market marketplace related to this Cursor resource

    • Extract text from pdf (component)pipedream · $2.99 · Related topic
    • Extract Text from Images & PDFs via Telegram with Mistral OCR to Markdownn8n · $24.99 · Related topic
    • Generating AI Videos with VEO3 and Distributing with Blotato Across Multiple Platformsn8n · $14.99 · Related topic
    • Microsoft Outlook AI Email Assistant with Contact Support from Monday and Airtablen8n · $14.99 · Related topic
    Browse all workflows