Advanced (अपडेट: 2/6/2026)

Claude Code से React Error Boundary सुरक्षित तरीके से लागू करें

Claude Code से React Error Boundary लागू करें: क्या पकड़ेगा, कहां लगाना है, reset, सुरक्षित logs, tests और prompts।

Claude Code से React Error Boundary सुरक्षित तरीके से लागू करें

React ऐप में सबसे खराब समस्या केवल एक chart टूटना नहीं है। असली समस्या तब होती है जब किसी छोटे component की render error पूरी app को blank screen बना देती है। अगर आप Claude Code से सिर्फ “error handling जोड़ दो” कहते हैं, तो वह कुछ try/catch लगा सकता है, लेकिन React render के दौरान आने वाली errors फिर भी सही तरह isolate नहीं होंगी।

यह guide दिखाती है कि Claude Code की मदद से React Error Boundary को सुरक्षित तरीके से कैसे लागू करें। Error Boundary component tree की सीमा है। जब कोई child component render के दौरान unexpected error throw करता है, तो यह पूरी screen गिराने के बजाय fallback UI दिखाती है। यह root cause नहीं सुधारती, लेकिन नुकसान सीमित करती है, user को अगला step देती है, और team को जांच के लिए useful log देती है।

Masa ने इसे admin dashboard में आजमाया। पहले prompt ने पूरी app को एक ही boundary से wrap किया। blank screen कम हुई, लेकिन एक revenue chart टूटने पर settings और billing भी छिप गए। logs में query string वाले URLs भी थे जिनमें email आ सकता था। जब prompt में placement, reset rule, PII redaction और tests साफ लिखे गए, तब Claude Code का diff review करने लायक बना।

React Docs से बुनियाद पक्की करें

Claude Code को code लिखने से पहले facts बताएं। React की official Component reference बताती है कि static getDerivedStateFromError fallback state बनाने के लिए है, और componentDidCatch logging जैसे side effects के लिए है। React की official error-boundaries lint documentation भी बताती है कि JSX के आसपास सामान्य try/catch render errors के लिए सही तरीका नहीं है।

सबसे जरूरी बात: Error Boundary सब कुछ नहीं पकड़ती। यह child components की render, lifecycle, या render flow में चलने वाले hook और memo की unexpected errors पकड़ सकती है। लेकिन click handler, सामान्य Promise rejection, setTimeout, server-side rendering और boundary के अपने fallback की error अलग से handle करनी पड़ती है।

जगहError Boundary पकड़ेगीproduction में तरीका
child component render में errorहांfallback UI दिखाएं और redacted log भेजें
render flow वाले hook या memo की errorआम तौर पर हांexpected failure पहले validate करें, unexpected exception boundary को दें
button click या form submit handlerनहींhandler में try/catch, जरूरत हो तो state से दोबारा throw
setTimeout, animation callback, सामान्य PromiseनहींPromise failure साफ handle करें और retry दें
server-side renderingनहींframework error page, server logs और HTTP status use करें
boundary का fallback खुद टूटेनहींfallback simple रखें और ऊपर एक boundary रखें
flowchart TD
  A["Child component render में error throw करता है"] --> B["सबसे नजदीकी Error Boundary"]
  B --> C["User-facing fallback UI"]
  B --> D["PII हटाकर error log"]
  E["Click handler या setTimeout fail होता है"] --> F["Local handle करें या state से boundary को दें"]
  F --> B

Route-Level और Component-Level Boundary अलग रखें

हर जगह boundary लगाने से design बेहतर नहीं होता। पूरी app पर एक boundary बहुत बड़ी scope है, और हर button पर boundary लगाने से page छोटे-छोटे fallback blocks से भर जाता है। Claude Code को साफ बताएं कि route-level boundary कहां चाहिए और component-level boundary कहां।

Route-level boundary एक screen responsibility को बचाती है: dashboard, settings, billing, editor, search या admin audit log। route change होने पर boundary reset होनी चाहिए, ताकि पुराने page की failed state नए page पर न जाए।

Component-level boundary page के अंदर independent region के लिए होती है। revenue chart, notification panel, Markdown preview, recommendation widget, third-party embed, या बड़ा JSON viewer अच्छे candidates हैं। सामान्य input, submit button, heading और icon को boundary से wrap करना ठीक नहीं। वहां validation और normal UI state बेहतर है।

तीन सवाल पूछें: क्या इस region के fail होने पर user बाकी काम कर सकता है; क्या यह region खुद retry, reload या reset हो सकता है; क्या log में feature name देने से जांच आसान होगी। यह Claude Code testing strategies से भी जुड़ता है, क्योंकि user जिस unit को retry करता है वही unit testable होनी चाहिए।

Copy-Paste Error Boundary Component

Shared Error Boundary अभी भी class component से बनेगी। बाकी app function components में रह सकती है। केवल यह wrapper React के error boundary lifecycle methods का उपयोग करता है।

// src/components/error-boundary/ErrorBoundary.tsx
import { Component, ErrorInfo, ReactNode } from "react";

export type ErrorBoundaryFallbackProps = {
  error: Error;
  resetErrorBoundary: () => void;
};

type ErrorBoundaryProps = {
  children: ReactNode;
  fallback?: ReactNode | ((props: ErrorBoundaryFallbackProps) => ReactNode);
  onError?: (error: Error, info: ErrorInfo) => void;
  onReset?: () => void;
  resetKeys?: ReadonlyArray<unknown>;
};

type ErrorBoundaryState = {
  error: Error | null;
};

function normalizeError(value: unknown): Error {
  if (value instanceof Error) return value;
  return new Error(typeof value === "string" ? value : "Unknown render error");
}

function changedArray(
  previous: ReadonlyArray<unknown> = [],
  next: ReadonlyArray<unknown> = [],
): boolean {
  return (
    previous.length !== next.length ||
    previous.some((item, index) => !Object.is(item, next[index]))
  );
}

export class ErrorBoundary extends Component<
  ErrorBoundaryProps,
  ErrorBoundaryState
> {
  state: ErrorBoundaryState = { error: null };

  static getDerivedStateFromError(error: unknown): ErrorBoundaryState {
    return { error: normalizeError(error) };
  }

  componentDidCatch(error: Error, info: ErrorInfo) {
    this.props.onError?.(normalizeError(error), info);
  }

  componentDidUpdate(previousProps: ErrorBoundaryProps) {
    if (
      this.state.error &&
      changedArray(previousProps.resetKeys, this.props.resetKeys)
    ) {
      this.resetErrorBoundary();
    }
  }

  resetErrorBoundary = () => {
    this.props.onReset?.();
    this.setState({ error: null });
  };

  render() {
    if (!this.state.error) return this.props.children;

    if (typeof this.props.fallback === "function") {
      return this.props.fallback({
        error: this.state.error,
        resetErrorBoundary: this.resetErrorBoundary,
      });
    }

    if (this.props.fallback) return this.props.fallback;

    return (
      <section role="alert" aria-labelledby="error-boundary-title">
        <h2 id="error-boundary-title">Something went wrong</h2>
        <p>Please retry. If the problem continues, contact support.</p>
        <button type="button" onClick={this.resetErrorBoundary}>
          Try again
        </button>
      </section>
    );
  }
}

fallback static node भी हो सकता है और function भी। function वाला form बेहतर है क्योंकि उसे error और resetErrorBoundary मिलते हैं। लेकिन UI में error.stack, raw API response, या internal debug text न दिखाएं। user को छोटा संदेश, safe action और जरूरत हो तो support reference चाहिए।

Fallback UI, Reset और Retry

fallback UI debug dump नहीं है, product UI है। इसे बताना चाहिए कि कौन सा हिस्सा बंद हुआ, user data बदला या नहीं, और कौन सा action safe है। deploy के बाद chunk loading error हो तो app reload button useful हो सकता है। सामान्य widget error में सिर्फ उस section को retry कराना बेहतर है।

// src/components/error-boundary/AppErrorFallback.tsx
import type { ErrorBoundaryFallbackProps } from "./ErrorBoundary";

export function AppErrorFallback({
  error,
  resetErrorBoundary,
}: ErrorBoundaryFallbackProps) {
  const reloadRecommended =
    /ChunkLoadError|Loading chunk|dynamically imported module/i.test(
      error.message,
    );

  return (
    <section
      role="alert"
      aria-labelledby="app-error-title"
      className="error-fallback"
    >
      <div>
        <p className="error-fallback__eyebrow">This section stopped working</p>
        <h2 id="app-error-title">We could not render this part of the page.</h2>
        <p>
          Your account data was not changed. Retry this section first, then
          reload the app if the same message appears again.
        </p>
      </div>

      <div className="error-fallback__actions">
        <button type="button" onClick={resetErrorBoundary}>
          Try again
        </button>
        {reloadRecommended ? (
          <button type="button" onClick={() => window.location.reload()}>
            Reload app
          </button>
        ) : null}
      </div>
    </section>
  );
}
/* src/components/error-boundary/error-fallback.css */
.error-fallback {
  border: 1px solid #d7dde8;
  border-radius: 8px;
  padding: 16px;
  background: #fff;
  color: #1f2937;
}

.error-fallback__eyebrow {
  margin: 0 0 4px;
  color: #6b7280;
  font-size: 0.875rem;
}

.error-fallback h2 {
  margin: 0 0 8px;
  font-size: 1.125rem;
}

.error-fallback__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

retry में common bug है boundary state को साफ करना, पर broken input को न बदलना। अगर वही props, वही cache, वही route state फिर throw करते हैं, button बेकार दिखेगा। resetKeys में route key, filters, user id, refresh counter या data version रखें।

Route और Component Implementation Example

React Router में route boundary एक पतला wrapper हो सकता है। यह example location.key से reset करता है और log में feature name भेजता है। Next.js और Remix में route error files होती हैं, लेकिन principle वही है: navigation पर reset और page-level failure को isolate करना।

// src/AppRoutes.tsx
import { lazy, ReactNode, Suspense } from "react";
import {
  createBrowserRouter,
  RouterProvider,
  useLocation,
} from "react-router-dom";
import { ErrorBoundary } from "./components/error-boundary/ErrorBoundary";
import { AppErrorFallback } from "./components/error-boundary/AppErrorFallback";
import { currentErrorContext, reportReactError } from "./lib/error-reporting";
import { Layout } from "./routes/Layout";

const DashboardPage = lazy(() => import("./routes/DashboardPage"));
const SettingsPage = lazy(() => import("./routes/SettingsPage"));

function RouteBoundary({
  children,
  feature,
}: {
  children: ReactNode;
  feature: string;
}) {
  const location = useLocation();

  return (
    <ErrorBoundary
      resetKeys={[location.key]}
      fallback={(props) => <AppErrorFallback {...props} />}
      onError={(error, info) => {
        void reportReactError(
          error,
          info.componentStack,
          currentErrorContext(feature),
        );
      }}
    >
      <Suspense fallback={<p>Loading...</p>}>{children}</Suspense>
    </ErrorBoundary>
  );
}

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      {
        path: "dashboard",
        element: (
          <RouteBoundary feature="dashboard">
            <DashboardPage />
          </RouteBoundary>
        ),
      },
      {
        path: "settings",
        element: (
          <RouteBoundary feature="settings">
            <SettingsPage />
          </RouteBoundary>
        ),
      },
    ],
  },
]);

export function AppRoutes() {
  return <RouterProvider router={router} />;
}

Component-level boundary उन regions के लिए रखें जो independently recover हो सकते हैं: charts, Markdown preview, recommendation panel, third-party embed और JSON viewer। हर form field के लिए boundary न बनाएं। payment decline, validation failure और expired session product state हैं।

Async और Event Handler Errors

Error Boundary click handlers और सामान्य async failures को अपने आप नहीं पकड़ती। expected failures local UI में रहें: field validation, authentication, payment decline। unexpected exceptions को state में रखकर अगले render में throw किया जा सकता है ताकि nearest boundary उन्हें handle करे।

// src/components/error-boundary/useAsyncBoundary.ts
import { useCallback, useState } from "react";

function toError(value: unknown): Error {
  if (value instanceof Error) return value;
  return new Error(typeof value === "string" ? value : "Unknown async error");
}

export function useAsyncBoundary() {
  const [error, setError] = useState<Error | null>(null);

  if (error) {
    throw error;
  }

  return useCallback((value: unknown) => {
    setError(toError(value));
  }, []);
}
// src/components/settings/SaveButton.tsx
import { useState } from "react";
import { useAsyncBoundary } from "../error-boundary/useAsyncBoundary";

type SaveButtonProps = {
  onSave: () => Promise<void>;
};

export function SaveButton({ onSave }: SaveButtonProps) {
  const [pending, setPending] = useState(false);
  const throwToBoundary = useAsyncBoundary();

  async function handleClick() {
    setPending(true);

    try {
      await onSave();
    } catch (error) {
      throwToBoundary(error);
    } finally {
      setPending(false);
    }
  }

  return (
    <button type="button" disabled={pending} onClick={handleClick}>
      {pending ? "Saving..." : "Save"}
    </button>
  );
}

Claude Code को साफ कहें कि हर async failure boundary में न भेजे। 400 response, invalid field, rate limit और expired session की local UI होनी चाहिए। boundary unexpected exception, corrupt response और render assumption failure के लिए है।

PII Leak किए बिना Logging

PII यानी ऐसी जानकारी जिससे व्यक्ति की पहचान हो सकती है: email, phone, name, address, token, card number या support form का free text। componentDidCatch client error report करने की अच्छी जगह है, लेकिन payload limited और redacted होना चाहिए।

log में feature, release, route pathname, error name, redacted message, stack और componentStack रखें। query string, form values, cookies, Authorization headers, raw API response और full URL न भेजें।

// src/lib/error-reporting.ts
type ClientErrorContext = {
  route: string;
  release: string;
  feature?: string;
  userHash?: string;
};

const REDACTIONS: Array<[RegExp, string]> = [
  [/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, "[redacted-email]"],
  [/\b(?:\d[ -]*?){13,19}\b/g, "[redacted-number]"],
  [/\b(token|secret|password|authorization)=([^&\s]+)/gi, "$1=[redacted]"],
  [/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]"],
];

export function redactText(value: string | undefined): string | undefined {
  if (!value) return value;

  return REDACTIONS.reduce(
    (text, [pattern, replacement]) => text.replace(pattern, replacement),
    value,
  );
}

export function currentErrorContext(feature?: string): ClientErrorContext {
  const env = (import.meta as unknown as {
    env?: Record<string, string | undefined>;
  }).env;

  return {
    route: typeof window === "undefined" ? "server" : window.location.pathname,
    release: env?.VITE_APP_VERSION ?? "dev",
    feature,
  };
}

export async function reportReactError(
  error: Error,
  componentStack: string | undefined,
  context: ClientErrorContext,
) {
  const payload = {
    name: redactText(error.name) ?? "Error",
    message: redactText(error.message) ?? "Unknown error",
    stack: redactText(error.stack),
    componentStack: redactText(componentStack),
    route: context.route,
    release: context.release,
    feature: context.feature,
    userHash: context.userHash,
  };

  const body = JSON.stringify(payload);

  if (typeof navigator !== "undefined" && navigator.sendBeacon) {
    const sent = navigator.sendBeacon(
      "/api/client-errors",
      new Blob([body], { type: "application/json" }),
    );
    if (sent) return;
  }

  await fetch("/api/client-errors", {
    method: "POST",
    headers: { "content-type": "application/json" },
    credentials: "omit",
    keepalive: true,
    body,
  });
}

server पर भी दोबारा redact करें। client-side redaction helpful है, लेकिन compliance boundary नहीं है। Claude Code से client और server दोनों layer बनवाएं और support correlation के लिए केवल hashed user id रखें।

Tests और Verification Commands

Error Boundary fail होने पर काम आती है, इसलिए broken path test करें। minimum tests: fallback render, onError call, retry reset।

// src/components/error-boundary/ErrorBoundary.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom/vitest";
import { ReactNode, useState } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ErrorBoundary } from "./ErrorBoundary";

function Bomb({ shouldThrow }: { shouldThrow: boolean }) {
  if (shouldThrow) {
    throw new Error("profile widget crashed");
  }

  return <p>Profile loaded</p>;
}

function RetryHarness({ onError }: { onError: ReturnType<typeof vi.fn> }) {
  const [broken, setBroken] = useState(true);

  return (
    <ErrorBoundary
      onError={onError}
      fallback={({ resetErrorBoundary }) => (
        <button
          type="button"
          onClick={() => {
            setBroken(false);
            resetErrorBoundary();
          }}
        >
          Retry profile
        </button>
      )}
    >
      <Bomb shouldThrow={broken} />
    </ErrorBoundary>
  );
}

function StaticFallback({ children }: { children: ReactNode }) {
  return (
    <ErrorBoundary fallback={<p>Could not load this panel.</p>}>
      {children}
    </ErrorBoundary>
  );
}

describe("ErrorBoundary", () => {
  let consoleErrorSpy: ReturnType<typeof vi.spyOn>;

  beforeEach(() => {
    consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
  });

  afterEach(() => {
    consoleErrorSpy.mockRestore();
  });

  it("renders fallback UI when a child throws", () => {
    render(
      <StaticFallback>
        <Bomb shouldThrow />
      </StaticFallback>,
    );

    expect(screen.getByText("Could not load this panel.")).toBeInTheDocument();
  });

  it("calls onError with the thrown error and component stack", () => {
    const onError = vi.fn();

    render(<RetryHarness onError={onError} />);

    expect(onError).toHaveBeenCalledTimes(1);
    expect(onError.mock.calls[0][0].message).toBe("profile widget crashed");
    expect(onError.mock.calls[0][1].componentStack).toContain("Bomb");
  });

  it("can reset and render children again", async () => {
    const user = userEvent.setup();
    const onError = vi.fn();

    render(<RetryHarness onError={onError} />);
    await user.click(screen.getByRole("button", { name: "Retry profile" }));

    expect(screen.getByText("Profile loaded")).toBeInTheDocument();
  });
});
npm install -D vitest @testing-library/react @testing-library/user-event @testing-library/jest-dom jsdom
npm run typecheck
npx vitest run src/components/error-boundary/ErrorBoundary.test.tsx
npm run build

Claude Code के लिए Safe Prompts

Add React Error Boundaries to this React + TypeScript app.

Constraints:
- Follow the official React Error Boundary model.
- Catch render errors from descendants, but handle event handlers and ordinary async failures separately.
- Implement a shared ErrorBoundary class, user-facing fallback UI, and reportReactError with PII redaction.
- Route-level boundaries must reset on navigation through resetKeys.
- Component-level boundaries should only wrap independent regions such as DashboardChart, MarkdownPreview, and RecommendationPanel.
- Do not log error.stack, query strings, form values, Authorization headers, cookies, or raw API responses without redaction.
- Add Vitest + Testing Library coverage for fallback UI, onError, and retry reset.
- Run npm run typecheck, npx vitest run, and npm run build, then report the results.

Read the existing routing, logging, and CSS conventions first. Keep the diff minimal.

Review prompt:

Review this diff only from the Error Boundary perspective.
List issues with boundary placement, async errors that are not caught, PII leakage, missing resetKeys, fallback accessibility, and missing tests.
Do not change code. Return file names and line numbers.

Use Cases और Pitfalls

पहला use case SaaS dashboard है। revenue chart, active users table, notification panel और third-party embed को अलग-अलग wrap करें। chart library bug settings या billing को block नहीं करना चाहिए। logs में dashboard.revenue-chart जैसा feature name रखें।

दूसरा use case content editor है। Markdown preview, image preview और AI summary panel fragile होते हैं। body editor और save button user का main work surface हैं। preview को boundary दें, पर save failure event handler में दिखाएं।

तीसरा use case ecommerce या signup है। card decline, stock shortage और validation failure boundary error नहीं, product state हैं। recommendation modules, campaign banners और review widgets को isolate किया जा सकता है।

चौथा use case admin audit log है। बड़ा JSON viewer formatting के दौरान throw कर सकता है। viewer को wrap करें, पूरी page को नहीं, ताकि operator filters बदल सके, CSV export कर सके या दूसरे user को देख सके।

common pitfalls हैं: JSX को try/catch से wrap करके सुरक्षित मान लेना; सभी async failures boundary को भेजना; full URL और query string log करना; UI में stack trace दिखाना; reset के बाद broken input न बदलना; बहुत छोटे-छोटे boundaries बनाकर page को fallback blocks से भर देना। team adoption के लिए implementation और review prompts को Claude Code commands बना सकते हैं। बड़े rollout के लिए next step को Claude Code training और implementation support से जोड़ें।

Summary

Error Boundary universal exception handler नहीं है। यह React render failures के लिए boundary है, जिसमें fallback UI और safe logging शामिल हैं। Claude Code का उपयोग करते समय capture scope, route-level और component-level placement, reset behavior, PII policy, tests और verification commands prompt में साफ लिखें।

dashboard test में पहले resetKeys और redaction rules तय करने से review time कम हुआ। एक widget crash होने पर पूरी app नहीं गिरी, और logs debugging के लिए useful रहे बिना user data expose किए।

#Claude Code #React #Error Boundary #error handling #TypeScript
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.