React: Error Boundaries and Debugging

Last updated: 2026-08-26

Tom was responsible for the e-commerce back-end, which experienced a "white screen" issue on the live site—users reported that the order details page was completely blank when opened, and the console displayed the erro Cannot read properties of indefinido (reading 'name'). The cause was an uncaught JavaScript erro thrown by a child component during the rendering phase, which caused the entire React component tree to crash. Tom needs to establish an erro-boundary system within the application to ensure that the failure of individual components does not affect the overall page usability.


1. What You'll Learn



2. Conceptual Diagrams

The figure below illustrates the Error Boundary capture process and the Profiler's analysis chain:

100%
flowchart TD
    A[React Component tree] --> B[Error Boundary]

    B --> C{Is there an error in the rendering of the child component??}
    C -->|No| D[Normal Rendering]
    C -->|is | E[getDerivedStateFromError]

    E --> F[Update state.hasError = true]
    F --> G[Rendering fallback UI]

    G --> H{Click to retry??}
    H -->|is | I[Reset state]
    I --> A
    H -->|No| J[Stay in the lower division UI]

    K[React DevTools Profiler] --> L[Recording Component Rendering]
    L --> M[Flame Pattern Analysis]
    M --> N["Identify Time-Consuming Components(Yellow/Red)"]
    N --> O[React.memo / useMemo Optimization]

    style B fill:#e3f2fd,stroke:#1565c0
    style E fill:#fff3e0,stroke:#e65100
    style G fill:#e8f5e9,stroke:#2e7d32
    style K fill:#f3e5f5,stroke:#7b1fa2


3. A Real-Life Scenario

Error Handling Level Scope Recovery Strategy Applicable Scenarios
try/catch Single asynchronous operation Retry/Fallback API requests, Promise operations
Error Boundary Child Component tree Rendering Error Fallback UI + Retry Button Component White Screen Protection
Global unhandledrejection Uncaught Promise errors Log reporting Catch-all monitoring
window.onerror Global Synchronization Errors Log Reporting Catch-All Monitoring
React DevTools Debugging During Development Troubleshooting Troubleshooting Performance/Rendering Issues

Tom's order details page consists of the following components:

TEXT 📖 Display only
OrderPage
  ├── OrderHeader      (Order Number、Status)
  ├── OrderItems       (Product List)
  │   └── OrderItem    × N(Single Item,Includes price calculation)
  ├── ShippingInfo     (Shipping Information)
  └── PaymentInfo      (Payment Information)

The cause of the online incident was that the price field was missing from the product data for a particular order, and when the OrderItem component accessed item.price.toFixed(2), it threw a TypeError error, causing the entire OrderPage to display a blank screen.

The correct approach is to wrap the OrderItems area with an Error Boundary so that even if the product list fails to render, the order header and payment information will still display properly. Additionally, Tom needs to learn how to use the React DevTools Profiler to identify performance bottlenecks.


(1) Error Boundary — Component-Level Safety Net

An Error Boundary is a declarative error-handling mechanism provided by React. When any component in a subtree throws an error during the rendering phase, in a lifecycle method, or in its constructor, the Error Boundary can catch that error and display a fallback UI, rather than causing the entire application to display a blank screen.

Note: Error Boundary can currently only be implemented using class components (React plans to provide a Hook version in future releases).

▶ Example 1: Generic ErrorBoundary Component

Output:

TEXT 📖 Display only
Error boundary catches rendering errors, shows fallback UI
TSX
import { Component, ErrorInfo, ReactNode } from 'react'

interface ErrorBoundaryProps {
  children: ReactNode
  /** Custom Downgrade UI */
  fallback?: ReactNode
  /** Error Callback(Submit Sentry etc.) */
  onError?: (error: Error, errorInfo: ErrorInfo) => void
}

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

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

  // Static Methods:Update Based on the Error state
  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error }
  }

  // Life Cycle:Performing side effects after catching an error(Log Reporting)
  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error('ErrorBoundary An error was caught:', error.message)
    console.error('Component Stack:', errorInfo.componentStack)

    // Reported to the error monitoring service
    if (this.props.onError) {
      this.props.onError(error, errorInfo)
    }

    // Can be integrated into actual projects Sentry:
    // Sentry.captureException(error, { extra: errorInfo })
  }

  handleReset = () => {
    this.setState({ hasError: false, error: null })
  }

  render() {
    if (this.state.hasError) {
      // Use Custom fallback Or default to a lower level UI
      if (this.props.fallback) {
        return this.props.fallback
      }

      return (
        <div
          role="alert"
          style={{
            padding: '32px 24px',
            margin: 16,
            background: '#fff2f0',
            border: '1px solid #ffccc7',
            borderRadius: 8,
            textAlign: 'center',
          }}
        >
          <h2 style={{ color: '#ff4d4f', margin: '0 0 12px' }}>
            A component error occurred
          </h2>
          <p style={{ color: '#666', marginBottom: 8, fontSize: 14 }}>
            {this.state.error?.message || 'An unknown error has occurred'}
          </p>
          <button
            onClick={this.handleReset}
            style={{
              padding: '6px 20px',
              background: '#ff4d4f',
              color: '#fff',
              border: 'none',
              borderRadius: 4,
              cursor: 'pointer',
              fontSize: 14,
            }}
          >
            Retry
          </button>
        </div>
      )
    }

    return this.props.children
  }
}

export default ErrorBoundary

Output:

TEXT 📖 Display only
TypeScript props: interface ButtonProps { text: string; onClick: () => void; color?: string }. Editor auto-completes, compile-time errors on misuse.

Using ErrorBoundary in Real-World Projects

TSX
// Layered Wrapping — Each independent area has its own ErrorBoundary
function OrderPage({ orderId }: { orderId: string }) {
  return (
    <div>
      {/* Order Header Information:It will display correctly even if there is an error below. */}
      <ErrorBoundary fallback={<p>Failed to load order</p>}>
        <OrderHeader orderId={orderId} />
      </ErrorBoundary>

      {/* Product List:Errors in one area do not affect other areas */}
      <ErrorBoundary
        onError={(err) => {
          // Rendering Error in the List of Submitted Products
          fetch('/api/log-error', {
            method: 'POST',
            body: JSON.stringify({ error: err.message, orderId }),
          })
        }}
      >
        <OrderItems orderId={orderId} />
      </ErrorBoundary>

      {/* Payment Information */}
      <ErrorBoundary>
        <PaymentInfo orderId={orderId} />
      </ErrorBoundary>
    </div>
  )
}

▶ Example 2: The UserProfile component with error recovery

Output:

TEXT 📖 Display only
ErrorBoundary catches render errors → shows fallback UI "Something went wrong" + "Retry" button. Child components crash gracefully.

In real-world scenarios, sometimes displaying a fallback UI alone isn’t enough—users may need to refresh specific data. Here’s an example of how to use an error boundary with a “Retry” feature:

TSX
import { useState } from 'react'
import ErrorBoundary from './ErrorBoundary'

// Simulating Data Retrieval That Results in Errors
function fetchUserData(userId: number) {
  return fetch(`/api/users/${userId}`).then(res => {
    if (!res.ok) throw new Error('Failed to retrieve user data')
    return res.json()
  })
}

// Data display components that may have rendering errors
function UserInfo({ userId }: { userId: number }) {
  const [user, setUser] = useState<any>(null)
  const [loading, setLoading] = useState(true)

  useState(() => {
    fetchUserData(userId)
      .then(setUser)
      .finally(() => setLoading(false))
  })

  if (loading) return <p>Loading......</p>

  // If user Data Structure Exception,An error may occur here
  return (
    <div>
      <h3>{user.name}</h3>         {/* possibly:Cannot read properties of undefined */}
      <p>{user.profile.bio}</p>    {/* possibly:Cannot read properties of undefined */}
    </div>
  )
}

// Outer Container:With retries key Mechanism
function UserProfile({ userId }: { userId: number }) {
  const [retryKey, setRetryKey] = useState(0)

  return (
    <ErrorBoundary
      key={retryKey}  // Change key It will unmount and remount the subtree
      fallback={
        <div style={{ padding: 24, textAlign: 'center' }}>
          <p>Error loading user information</p>
          <button onClick={() => setRetryKey(k => k + 1)}>
            Retry Loading
          </button>
        </div>
      }
    >
      <UserInfo userId={userId} />
    </ErrorBoundary>
  )
}

Key Tip: key={retryKey} Have the ErrorBoundary unmount and recreate its subtree when a retry is triggered, thereby resetting the state of all child components.


(2) Errors That Cannot Be Caught by Error Boundaries

Error Boundaries are not a panacea; they cannot catch the following four types of errors:

Error Type Cause Solution
Errors in Event Handling Event Handlers Do Not Execute During the Rendering Phase Enclose Event Handling Logic in a try/catch Block
Errors in Asynchronous Code setTimeout / Promise callbacks do not occur within the React rendering cycle Use try/catch or Promise.catch
Errors in Server-Side Rendering (SSR) Error Boundaries Only Take Effect on the Client Side Wrap Rendering in try/catch Blocks for SSR
Error Boundary's own error It throws an error that cannot be caught by itself Wrap it in another Error Boundary at the outermost level

Event Handling + Proper Error Handling in Asynchronous Code

TSX
function PaymentForm() {
  async function handleSubmit() {
    try {
      const result = await submitPayment()
      // Processed successfully
    } catch (error) {
      // Asynchronous errors are caught here,Error Boundary That's none of my business
      console.error('Payment Failed:', error)
      // Display Error UI(For example, setting state)
      setError(error instanceof Error ? error.message : 'Payment Failed')
    }
  }

  // Errors in the event must also be used try/catch
  function handleClick() {
    try {
      processPayment()
    } catch (error) {
      setError('Processing Failed,Please try again.')
    }
  }
}

(3) Analyzing Performance Using the React DevTools Profiler

The Profiler tab in React DevTools is a core tool for analyzing component rendering performance. It generates a "flame graph" that visually illustrates the rendering time for each component.

Instructions for Use

TEXT 📖 Display only
1. Open your browser DevTools → Components Tabs
2. Switch to Profiler Sublabel
3. Click the blue record button(Start Recording)
4. Performing actions on the page(Click、Scrolling, etc.)
5. Click the Stop button(End Recording)
6. View the flame diagram

How to Interpret Flame Charts

TEXT 📖 Display only
┌────────────────────────────────────────────┐
│  App (0.3ms)                               │
│  ├── Navbar (0.2ms)                        │
│  ├── OrderPage (2.1ms)                     │
│  │   ├── OrderHeader (0.4ms)  ── Gray     │
│  │   ├── OrderItems (1.5ms)   ── Yellow     │
│  │   │   └── OrderItem × 20 (each 0.3ms)     │
│  │   └── PaymentInfo (0.2ms) ── Gray      │
│  └── Footer (0.1ms)                        │
└────────────────────────────────────────────┘

▶ Example 3: Measuring Rendering Time with the Profiler Component

Output:

TEXT 📖 Display only
Profiler logs render timings: mount 45ms, update 12ms. Identify slow components and optimize with memo/callback

React's built-in <Profiler> component can accurately measure the rendering time of a specific component in your code, making it suitable for automated monitoring of performance metrics:

TSX
import { Profiler } from 'react'

type ProfilerPhase = 'mount' | 'update' | 'nested-update'

interface ProfileMetrics {
  id: string
  phase: ProfilerPhase
  actualDuration: number      // Actual rendering time for this render(milliseconds)
  baseDuration: number        // Worst-case runtime for a subtree
  startTime: number           // Render Start Timestamp
  commitTime: number          // Submit to DOM timestamp
  interactions: Set<any>      // Related Interaction Tracking
}

// Performance Monitoring Callbacks
function onRenderCallback(
  id: string,
  phase: ProfilerPhase,
  actualDuration: number,
  baseDuration: number,
  startTime: number,
  commitTime: number,
) {
  // Record to the performance log
  if (actualDuration > 16) {  // More than 16ms = Frame drop threshold (60fps)
    console.warn(
      `[Performance Alerts] ${id} in  ${phase} Time Taken per Stage ${actualDuration.toFixed(1)}ms,` +
      `More than 16ms Frame Budget!`
    )

    // Reported to the performance monitoring system
    // reportPerformance({ id, phase, actualDuration, baseDuration })
  }

  // Output from the development environment to the console
  if (process.env.NODE_ENV === 'development') {
    console.table({
      'Components': id,
      'Phase': phase,
      'Actual time taken(ms)': actualDuration.toFixed(1),
      'Benchmark Duration(ms)': baseDuration.toFixed(1),
    })
  }
}

// Big Data List——Potential Performance Bottlenecks
function ProductList({ products }: { products: Product[] }) {
  return (
    <Profiler id="ProductList" onRender={onRenderCallback}>
      <div style={{ display: 'grid', gap: 16, gridTemplateColumns: 'repeat(3, 1fr)' }}>
        {products.map(product => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </Profiler>
  )
}

Common Performance Optimization Strategies

TSX
// 1. React.memo — Avoid Unnecessary Re-rendering
const ProductCard = React.memo(function ProductCard({
  product,
}: {
  product: Product
}) {
  return (
    <div style={{ border: '1px solid #eee', padding: 16, borderRadius: 8 }}>
      <img src={product.image} alt={product.name} width="100%" />
      <h4>{product.name}</h4>
      <p>${product.price}</p>
    </div>
  )
})

// 2. useMemo — Cache the results of expensive computations
function OrderSummary({ items }: { items: OrderItem[] }) {
  const totalPrice = useMemo(() => {
    return items.reduce((sum, item) => {
      // Assuming that complex currency conversions were performed here
      return sum + convertCurrency(item.price, item.currency)
    }, 0)
  }, [items])

  return <p>Total:${totalPrice.toFixed(2)}</p>
}

// 3. useCallback — Stable function references
function OrderList({ orders, onSelect }: {
  orders: Order[]
  onSelect: (id: string) => void
}) {
  // ✅ use  useCallback Keep references consistent
  const handleSelect = useCallback((id: string) => {
    onSelect(id)
  }, [onSelect])

  return orders.map(order => (
    <OrderRow key={order.id} order={order} onSelect={handleSelect} />
  ))
}

(4) Debugging Using the React DevTools Components Panel

In addition to the Profiler, the Components panel in React DevTools is also a powerful tool for everyday debugging:

Feature Purpose Operation
Browse the component tree View the component hierarchy Click DevTools → Components
View Props/State in Real Time Check the component's current state Select a component to view the right-hand panel
Directly modify the state Test the UI in different states Double-click a state value to edit it directly
Search Component Quick Locate Component Ctrl+F Enter component name
Go to source code View component implementation Click the <> icon
TEXT 📖 Display only
// DevTools Components Panel Examples
<OrderPage>
  <ErrorBoundary>
    <OrderHeader
      orderNumber="ORD-2026-0001"     ← Props Real-time Display
      status="shipped"                 ← Can be edited directly during testing
    />
  </ErrorBoundary>
  <ErrorBoundary>
    <OrderItems>
      <OrderItem product={...} />      ← State Expand to view
      <OrderItem product={...} />
    </OrderItems>
  </ErrorBoundary>
</OrderPage>

▶ Example 4: Handling API Request Errors—Retrieving Data with Retry Mechanisms

Output:

TEXT 📖 Display only
Async data fetching with loading/error/success states
JSX
function useFetchWithRetry(url, maxRetries = 3) {
  const [data, setData] = useState(null)
  const [error, setError] = useState(null)
  const [loading, setLoading] = useState(true)
  const [retries, setRetries] = useState(0)

  const fetchData = useCallback(async () => {
    setLoading(true)
    setError(null)
    try {
      const res = await fetch(url)
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      const json = await res.json()
      setData(json)
    } catch (err) {
      if (retries < maxRetries) {
        setRetries(r => r + 1)
        setTimeout(fetchData, 1000 * (retries + 1))
      } else {
        setError(err.message)
      }
    } finally {
      setLoading(false)
    }
  }, [url, retries, maxRetries])

  useEffect(() => { fetchData() }, [url])

  return { data, error, loading, retries, refetch: () => { setRetries(0); fetchData() } }
}

function UserList() {
  const { data: users, error, loading, retries, refetch } = useFetchWithRetry('/api/users')

  if (loading) return <p>Loading... {retries > 0 && `(retry ${retries})`}</p>
  if (error) return (
    <div style={{ padding: 20, textAlign: 'center' }}>
      <p style={{ color: '#ff4d4f' }}>Error: {error}</p>
      <button onClick={refetch} style={{ padding: '8px 16px', cursor: 'pointer' }}>Retry</button>
    </div>
  )

  return (
    <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Custom hook returns { data, loading, error }. Usage: const { data, loading } = useFetch("/api/users"). Auto-fetches on mount.

▶ Example 5: Global Error Monitoring—Sentry Integration

Output:

TEXT 📖 Display only
Uses useeffect
JSX
// lib/errorReporting.ts
const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN

function initErrorReporting() {
  if (typeof window === 'undefined') return
  if (!SENTRY_DSN) return

  // Sentry.init({ dsn: SENTRY_DSN, ... })
  // Simplified Example:Simulation Using Global Event Listeners

  window.addEventListener('unhandledrejection', (event) => {
    console.error('Unhandled Promise:', event.reason)
    reportError({
      type: 'unhandledrejection',
      message: event.reason?.message || String(event.reason),
      stack: event.reason?.stack,
      timestamp: new Date().toISOString(),
    })
  })

  window.addEventListener('error', (event) => {
    console.error('Global Error:', event.error)
    reportError({
      type: 'window.error',
      message: event.message,
      filename: event.filename,
      lineno: event.lineno,
      timestamp: new Date().toISOString(),
    })
  })
}

function reportError(payload) {
  fetch('/api/errors', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  }).catch(() => {})
}

// app/layout.tsx
function RootLayout({ children }) {
  useEffect(() => { initErrorReporting() }, [])
  return <html><body>{children}</body></html>
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
fetch("/api/users") → response.json() → setUsers(data). Basic GET request on component mount.

❓ FAQ

Q Can Error Boundary catch all errors?
A No. Error Boundaries only catch errors that occur during the rendering phase, in lifecycle methods, and in constructors. The following four types of errors cannot be caught: errors in event handlers (use try/catch), errors in asynchronous code (use Promise.catch), errors during server-side rendering, and errors within the Error Boundary itself. When designing error handling, you need to combine try/catch and Error Boundaries in a layered approach.
Q What do "gray" and "blue" represent in the Profiler's flame graph?
A Gray indicates that the component was not re-rendered during this commit (good performance). Blue indicates that the component was re-rendered, but the time taken was within the normal range. Yellow or red indicates that rendering took a long time and needs optimization. The goal is to keep most components gray or light blue during interactions.
Q Does React.memo automatically optimize all components?
A No. React.memo only performs shallow comparisons. If props contain objects or arrays, each render will result in new references, causing the memo to fail. In this case, you need to use useMemo / useCallback to keep the references stable, or pass a second argument—a custom comparison function React.memo(Comp, (prev, next) => deepEqual(prev, next))—to React.memo.
Q Does the <Profiler> component affect performance in a production environment?
A According to the official React documentation, the <Profiler> component incurs a slight performance overhead in a production environment. It is recommended to use it only in development environments or to control it via environment variables: {process.env.NODE_ENV === 'development' && <Profiler>...}. When performance monitoring is required in production, consider using dedicated performance monitoring libraries (such as web-vitals) or Sentry’s performance tracing features.
Q Can an Error Boundary be written as a function component?
A Not at this time. Error Boundaries rely on two lifecycle methods, getDerivedStateFromError and componentDidCatch, which are only supported by class components. The React team has indicated that a Hook version may be available in the future, but for now (React 18/19), they can only be implemented using class components. You can create a class-based Error Boundary component and then wrap it in a function component to handle the error recovery logic.

📖 Summary


📝 Exercises

  1. Create an ErrorBoundary component and use it hierarchically within OrderPage: Wrap OrderHeader, OrderItems, and PaymentInfo in separate ErrorBoundary components. Manually trigger a rendering error (such as passing incorrect props) to verify that only the affected area displays the fallback UI, while the rest of the interface renders normally.
  2. Use the Profiler component to measure the rendering time of a component containing 100 list items. After optimizing with React.memo, measure the rendering time again and compare the difference in actualDuration between the two measurements to verify the effectiveness of the optimization.
  3. Open the React DevTools Profiler in Chrome, record a page interaction (such as a search, filter, or sort), identify the component that takes the longest to render in the flame graph, analyze the cause, and optimize it using useMemo / useCallback.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏