Skip to content
Laviyx Learn

Try

React course menu

React Error Boundaries: Catching Rendering Errors

Stop one broken component from crashing your whole React app: what happens on a rendering error, writing an error boundary class component, fallback UI with a retry button, where to place boundaries, what boundaries do not catch, and the react-error-boundary library.

Intermediate9 min readUpdated
On this page

If a component throws an error while rendering, React removes the whole app from the screen, leaving a blank page, because it cannot trust any part of a broken tree. An error boundary catches errors from the components inside it and shows a fallback instead, so the rest of the app keeps working.

Writing an error boundary#

Error boundaries are one of the few things that still require a class component, because they use two class lifecycle methods:

  • static getDerivedStateFromError(error): returns new state so the next render shows the fallback;
  • componentDidCatch(error, info): for side effects such as logging the error to a monitoring service.
JSX
import { Component, useState } from "react";

class ErrorBoundary extends Component {
  state = { error: null };

  static getDerivedStateFromError(error) {
    return { error };
  }

  componentDidCatch(error, info) {
    // In a real app, send this to an error-reporting service.
    console.error("Caught by boundary:", error.message);
  }

  render() {
    if (this.state.error) {
      return (
        <div style={{ background: "#fff5f5", border: "1px solid #ffc9c9", padding: 10, borderRadius: 6 }}>
          <p style={{ margin: "0 0 6px" }}>⚠️ This widget crashed: {this.state.error.message}</p>
          <button onClick={() => this.setState({ error: null })}>Try again</button>
        </div>
      );
    }
    return this.props.children;
  }
}

function RiskyCounter() {
  const [count, setCount] = useState(0);
  if (count === 3) throw new Error("Count reached 3!");
  return <button onClick={() => setCount(count + 1)}>Count: {count} (crashes at 3)</button>;
}

export default function App() {
  return (
    <div>
      <h4 style={{ margin: "0 0 8px" }}>Dashboard</h4>
      <ErrorBoundary>
        <RiskyCounter />
      </ErrorBoundary>
      <p>This text is outside the boundary and stays visible.</p>
    </div>
  );
}
Result

Click until the count reaches 3: the counter is replaced by the fallback, while the heading and the text below keep working. "Try again" clears the error and renders the counter again, starting fresh at 0, because the crashed component was removed and its state was lost.

Where to put boundaries#

  • One near the top of the app, showing a friendly "Something went wrong" page instead of a blank screen.
  • Around independent widgets (a chart, a comments section, a sidebar), so one failing part does not take down the page.
  • Around each route or page section in larger apps.

What error boundaries do not catch#

Boundaries catch errors thrown while rendering, in lifecycle methods, and in constructors of the components below them. They do not catch errors in:

  • event handlers (use try/catch inside the handler and set error state);
  • asynchronous code such as setTimeout or a fetch callback (catch the error and store it in state, then render or throw it during the next render);
  • server-side rendering;
  • the error boundary itself (errors there go to the next boundary above).
JSX
import { useState } from "react";

export default function App() {
  const [error, setError] = useState(null);

  function handlePay() {
    try {
      throw new Error("Payment service unavailable");
    } catch (err) {
      setError(err.message);
    }
  }

  return (
    <div>
      <button onClick={handlePay}>Pay ₹499</button>
      {error && <p style={{ color: "crimson" }}>Could not pay: {error}</p>}
    </div>
  );
}
Result

react-error-boundary#

Most projects use the small react-error-boundary library instead of writing the class themselves. It provides an ErrorBoundary component with fallbackRender, a reset function, and a useErrorBoundary hook for sending errors from event handlers and async code to the nearest boundary:

JSX
import { ErrorBoundary } from "react-error-boundary";

function Fallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong: {error.message}</p>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

<ErrorBoundary FallbackComponent={Fallback}>
  <Comments />
</ErrorBoundary>;

(This snippet needs npm install react-error-boundary, so it is shown as code rather than as a live preview.)

Common mistakes#

Practice#

Practice exercise

Wrap two "product widgets" in separate error boundaries. One renders normally; the other throws because its product data is missing (product is undefined). Show that the first widget still works.

Show hint

Reuse the ErrorBoundary class. A ProductWidget({ product }) that reads product.name throws a TypeError when product is undefined.

Show solution
JSX
import { Component } from "react";

class ErrorBoundary extends Component {
  state = { error: null };
  static getDerivedStateFromError(error) {
    return { error };
  }
  render() {
    if (this.state.error) return <p style={{ color: "crimson" }}>Widget unavailable.</p>;
    return this.props.children;
  }
}

function ProductWidget({ product }) {
  return <p>{product.name}: ₹{product.price.toFixed(2)}</p>;
}

export default function App() {
  return (
    <div>
      <ErrorBoundary>
        <ProductWidget product={{ name: "Desk lamp", price: 1299 }} />
      </ErrorBoundary>
      <ErrorBoundary>
        <ProductWidget product={undefined} />
      </ErrorBoundary>
    </div>
  );
}
Result

Key takeaway#

Next: React with TypeScript.

Finished this lesson?