# How to Generate Proper Error Boundaries with Cursor

- Tool: Cursor
- Difficulty: Beginner
- Time required: 15-20 min
- Compatibility: Cursor Free+, React 18, TypeScript 5+
- Last updated: March 2026

## TL;DR

Cursor can generate fully typed React 18 error boundary components when you give it explicit instructions via .cursorrules and a precise Cmd+K or Chat prompt. Include the component tree structure, error state shape, and fallback UI requirements. With the right prompt, Cursor produces class-based boundaries with TypeScript generics, a reset mechanism, and an optional logging hook — saving you from runtime white-screen crashes.

## Generate typed React 18 error boundaries with Cursor

React error boundaries must still be class components in React 18 — hooks cannot catch render-phase errors. Without explicit guidance, Cursor sometimes generates functional components that silently skip the getDerivedStateFromError lifecycle, leaving crashes unhandled. This tutorial shows you how to lock Cursor to the correct pattern: a class-based ErrorBoundary with typed props and state, a fallback render prop, a reset callback, and an optional onError hook for Sentry or similar services. You will configure .cursorrules so the constraint is permanent, then use Chat (Cmd+L) and Composer (Cmd+I) to wrap specific subtrees automatically.

## Before you start

- Cursor installed (Free plan or above)
- A React 18 project with TypeScript configured
- Basic familiarity with Cursor Chat (Cmd+L) and .cursorrules
- React Router or similar already set up if you want route-level boundaries

## Step-by-step guide

### 1. Add error boundary rules to .cursorrules

Open or create .cursorrules in your project root. This file is read by Cursor for every prompt, so rules here propagate automatically. Add a section that prohibits functional error boundaries and mandates TypeScript generics on state and props. Be explicit: Cursor responds better to 'never use functional components for error boundaries' than to vague style preferences. Save the file — Cursor picks it up without a restart.

```
# .cursorrules

## React Error Boundaries
- Error boundaries MUST be class components implementing getDerivedStateFromError and componentDidCatch.
- NEVER generate functional error boundaries — React 18 does not support them.
- Always type ErrorBoundaryProps with a 'fallback' render prop: (error: Error, reset: () => void) => React.ReactNode
- Always type ErrorBoundaryState as { hasError: boolean; error: Error | null }
- Include a 'resetError' method that sets state back to { hasError: false, error: null }
- Call props.onError(error, info) inside componentDidCatch if the prop is provided
- Export the component as a named export, not default
```

> Pro tip: Put the error boundary rules under a clearly labeled heading in .cursorrules. Cursor uses headings to prioritise context when the file is long.

**Expected result:** Every future Cursor suggestion involving error boundaries will use the class-based pattern with typed state and props.

### 2. Generate the base ErrorBoundary component with Chat

Open Cursor Chat with Cmd+L. Type the prompt below exactly. The @codebase reference lets Cursor scan your existing component structure so it can match your naming and import conventions. The prompt specifies the file path, TypeScript strictness, and all required lifecycle methods. Review the generated diff before accepting — check that the fallback prop is typed correctly and that resetError appears as a bound method.

```
Generate a typed React 18 error boundary at src/components/ErrorBoundary.tsx.
Requirements:
- Class component, strict TypeScript, no 'any'
- Props: { fallback: (error: Error, reset: () => void) => React.ReactNode; onError?: (error: Error, info: React.ErrorInfo) => void; children: React.ReactNode }
- State: { hasError: boolean; error: Error | null }
- Implement getDerivedStateFromError and componentDidCatch
- componentDidCatch must call this.props.onError if provided
- Include a resetError arrow method
- Use named export
```

> Pro tip: If Cursor generates a functional component instead, type 'This must be a class component — React 18 does not support functional error boundaries' as a follow-up. The reminder in context usually fixes it instantly.

**Expected result:** Cursor creates src/components/ErrorBoundary.tsx with typed class, lifecycle methods, and reset handler.

### 3. Wrap a route-level subtree using Composer

Open Composer with Cmd+I. Reference the page component you want to protect using @file syntax. Ask Cursor to wrap the JSX return in your ErrorBoundary, providing a styled fallback UI that shows the error message and a 'Try again' button that calls reset. Composer can edit multiple files in one pass — it will update the import in the page file and add the boundary wrapper automatically. Review both file diffs before clicking Accept All.

```
Using @file src/pages/Dashboard.tsx and @file src/components/ErrorBoundary.tsx:

Wrap the entire JSX returned from Dashboard in <ErrorBoundary>.
The fallback prop should render a centered div with:
- An h2 reading 'Something went wrong'
- A <p> showing error.message
- A <button onClick={reset}>Try again</button> styled with Tailwind
```

> Pro tip: Use Composer Agent mode (toggle in the Composer panel) when you have more than two files to update. Agent mode can create the boundary file and update all usage sites in a single run.

**Expected result:** Dashboard.tsx now imports ErrorBoundary and wraps its return JSX with the boundary and a styled fallback.

### 4. Add a production error-logging hook

Select just the componentDidCatch method body in ErrorBoundary.tsx, then press Cmd+K. Use the inline edit to instruct Cursor to add a call to an external reporting function. This keeps the boundary decoupled — the hook accepts the error and info but can be swapped for Sentry, Datadog, or a custom endpoint. Cmd+K only edits the selected code, so the rest of the file stays untouched.

```
Extend componentDidCatch to also call logErrorToService(error, info.componentStack).
logErrorToService should be imported from src/utils/errorLogger.ts.
Create that file if it does not exist: export it as an async function that accepts (error: Error, stack: string | null | undefined) => Promise<void> and currently just console.errors in development (NODE_ENV check).
```

> Pro tip: After Cursor creates errorLogger.ts, open it and press Cmd+K again to replace the console.error stub with a real fetch call to your logging endpoint — Cursor will keep the TypeScript types intact.

**Expected result:** componentDidCatch calls logErrorToService. A new src/utils/errorLogger.ts file is created with a typed, environment-aware stub.

### 5. Verify and test the boundary

Open Cursor Chat and ask it to generate a minimal test component that deliberately throws inside render, then add a Jest + React Testing Library test that mounts it inside ErrorBoundary and asserts the fallback renders. Paste the test file path back into Chat with @file so Cursor can check the imports match your project's test setup. Run the tests from your terminal — if the boundary is correctly implemented, the 'Something went wrong' fallback text should appear in the test output.

```
Generate two files:
1. src/components/__tests__/ErrorBoundary.test.tsx
   - Uses @testing-library/react and @testing-library/jest-dom
   - Creates a ThrowingComponent that throws new Error('test error') on render
   - Wraps it in <ErrorBoundary fallback={(err, reset) => <button onClick={reset}>{err.message}</button>}>
   - Asserts fallback renders 'test error'
   - Asserts clicking the button removes the fallback (reset works)
2. No separate ThrowingComponent file — define it inline in the test
```

> Pro tip: React Testing Library suppresses console.error for expected errors. Add jest.spyOn(console, 'error').mockImplementation(() => {}) in a beforeEach block to keep test output clean.

**Expected result:** Test file is generated. Running Jest shows two passing tests: fallback renders and reset restores children.

## Complete code example

File: `src/components/ErrorBoundary.tsx`

```typescript
import React from 'react';
import { logErrorToService } from '../utils/errorLogger';

interface ErrorBoundaryProps {
  fallback: (error: Error, reset: () => void) => React.ReactNode;
  onError?: (error: Error, info: React.ErrorInfo) => void;
  children: React.ReactNode;
}

interface ErrorBoundaryState {
  hasError: boolean;
  error: Error | null;
}

export class ErrorBoundary extends React.Component<
  ErrorBoundaryProps,
  ErrorBoundaryState
> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { hasError: false, error: null };
    this.resetError = this.resetError.bind(this);
  }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, info: React.ErrorInfo): void {
    if (this.props.onError) {
      this.props.onError(error, info);
    }
    void logErrorToService(error, info.componentStack);
  }

  resetError(): void {
    this.setState({ hasError: false, error: null });
  }

  render(): React.ReactNode {
    const { hasError, error } = this.state;

    if (hasError && error) {
      return this.props.fallback(error, this.resetError);
    }

    return this.props.children;
  }
}

// src/utils/errorLogger.ts
// export async function logErrorToService(
//   error: Error,
//   stack: string | null | undefined
// ): Promise<void> {
//   if (process.env.NODE_ENV === 'development') {
//     console.error('[ErrorBoundary]', error.message, stack);
//     return;
//   }
//   await fetch('/api/log-error', {
//     method: 'POST',
//     headers: { 'Content-Type': 'application/json' },
//     body: JSON.stringify({ message: error.message, stack }),
//   });
// }
```

## Common mistakes

- **Asking Cursor to 'add an error boundary' without specifying class vs functional** — Cursor may generate a functional wrapper using try/catch in useEffect, which does not catch render-phase errors — the most common type of crash. Fix: Always include 'class component' and 'getDerivedStateFromError' in your prompt. Add the constraint to .cursorrules so it applies to every session.
- **Forgetting to bind resetError in the constructor** — If resetError is not bound, calling it from the fallback prop loses the 'this' context and throws a secondary error inside the already-errored boundary. Fix: Ask Cursor to bind all methods in the constructor, or use arrow function class properties. Add a note to .cursorrules: 'Bind all class methods in constructor or use arrow properties.'
- **Placing the error boundary at the very top of the component tree only** — A single top-level boundary catches everything but shows one generic fallback for the entire app — a poor user experience that hides which feature failed. Fix: Use Composer to add boundaries at route level and around critical widgets. Prompt Cursor: 'Add an ErrorBoundary wrapper around each route in src/App.tsx with a route-specific fallback.'
- **Not testing that reset actually restores children** — The reset handler is easy to wire incorrectly — state updated but the boundary re-renders with the same error, looping forever. Fix: Generate the test described in Step 5. The test that clicks reset and asserts children re-appear is the fastest way to catch this bug.

## Best practices

- Define ErrorBoundaryProps and ErrorBoundaryState as separate named interfaces, never inline — this makes the types reusable across multiple boundary instances.
- Always provide an onError prop hook so callers can plug in Sentry, Datadog, or a custom endpoint without modifying the boundary component itself.
- Add route-level boundaries around every major page and feature-level boundaries around high-risk widgets like data tables and charts.
- Keep the fallback UI self-contained with no external data dependencies — it must render even if the rest of the app is broken.
- Store the error in component state so the fallback can display a user-friendly message derived from error.message.
- Use the getDerivedStateFromError static method for state updates and componentDidCatch for side effects (logging) — mixing the two in one method is a common anti-pattern.
- Add the boundary pattern to .cursorrules once and never repeat it in individual prompts — consistent rules across all sessions prevent drift.

## Frequently asked questions

### Why does Cursor sometimes generate a functional component when I ask for an error boundary?

Without explicit constraints, Cursor picks the most common modern React pattern, which is functional. Add 'Error boundaries MUST be class components' to .cursorrules and include 'class component with getDerivedStateFromError' in every related prompt.

### Can I use the same ErrorBoundary component at multiple levels of my component tree?

Yes. The component is stateless between instances, so you can place it at the route level, around individual widgets, or both. Each instance maintains its own hasError state independently.

### Does Cursor's Tab completion respect my .cursorrules when it autocompletes JSX with error boundaries?

Tab completion uses nearby code as context, not .cursorrules directly. After you have the typed ErrorBoundary component in your project, Tab will infer the correct prop shape from the TypeScript definitions automatically.

### How do I get Cursor to add error boundaries to an existing codebase with many routes?

Open Composer (Cmd+I) in Agent mode and prompt: 'Find every top-level route component under src/pages and wrap its JSX return in <ErrorBoundary> imported from src/components/ErrorBoundary.tsx. Use a generic fallback for now.' Agent mode will edit multiple files in one pass.

### Should the fallback UI fetch any data or use React hooks?

No. The fallback renders inside a broken subtree, so any hook that depends on context or external state may also fail. Keep the fallback purely presentational — static markup, the error message string, and the reset button only.

### Can I generate error boundaries for React Native with the same Cursor prompts?

Yes, with small changes. Replace React.ReactNode with React.ReactElement in the fallback type, and swap Tailwind class names for React Native StyleSheet. Add 'This is a React Native project — use StyleSheet, not Tailwind' to your prompt.

---

Source: https://www.rapidevelopers.com/cursor-tutorial/how-to-instruct-cursor-ai-to-add-typed-error-boundaries-in-react-18-for-safer-ui-rendering
© RapidDev — https://www.rapidevelopers.com/cursor-tutorial/how-to-instruct-cursor-ai-to-add-typed-error-boundaries-in-react-18-for-safer-ui-rendering
