By WebNest Studio Engineering Team
React Tutorial
Error Boundaries in React
A JavaScript error thrown while rendering, in a lifecycle method, or in a constructor anywhere in a component tree will, by default, unmount the entire tree above it — leading to a blank screen for the whole app just because one small component failed. Error boundaries are React's mechanism for catching those rendering errors in a part of the tree and showing a fallback UI instead of crashing everything.
Error boundaries are one of the few remaining pieces of modern React that must be written as a class component, because React does not yet provide hook equivalents for the two lifecycle methods they require: static getDerivedStateFromError() and componentDidCatch(). Function components can be wrapped by an error boundary, but they cannot currently serve as one themselves.
Why it matters
One uncaught error during rendering makes React remove the whole component tree, leaving the user with a blank white page. An error boundary contains the damage: the broken part is replaced by a fallback and the rest of the application keeps working. Every production application should have at least one.
Why Hooks Cannot Replace Error Boundaries (Yet)
Error boundaries rely on getDerivedStateFromError, a static class method called during the "render" phase to compute fallback state, and componentDidCatch, an instance method called during the "commit" phase for side effects like logging. React has not introduced hook equivalents for either, so as of the current stable React releases, error boundaries must be class components. This is a deliberate and well-known exception to the "hooks-only" style used everywhere else in modern React code.
What Error Boundaries Catch (and What They Don't)
Error boundaries catch errors thrown during rendering, in lifecycle methods, and in constructors of the components below them in the tree. They do not catch errors inside event handlers (use a normal try/catch there), errors in asynchronous code such as setTimeout or promise callbacks, errors during server-side rendering, or errors thrown in the error boundary's own code.
- Caught: errors thrown while rendering a child component, or in a child's lifecycle/constructor.
- Not caught: errors inside onClick and other event handlers — wrap those in a regular try/catch instead.
- Not caught: errors in async code (fetch callbacks, setTimeout, promises) unless they end up being thrown during a subsequent render.
Why This Matters in Production
Without an error boundary, one unexpected null value, malformed API response, or third-party bug in a small widget can blank out an entire page for every user, with no way to recover except a manual refresh. Placing error boundaries strategically — around a page-level layout, around each major widget, or around a specific risky feature — contains the damage: the rest of the application keeps working, the user sees a friendly fallback message instead of a blank screen, and the boundary's componentDidCatch is a natural place to report the error to a monitoring service.
Example
A reusable class-based ErrorBoundary wrapping a function component that might throw
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render shows the fallback UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Side effect: log the error to a monitoring service
console.error('Caught by ErrorBoundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h2>Something went wrong with this section.</h2>;
}
return this.props.children;
}
}
function BuggyProfile({ user }) {
// Throws if "user" is null — a realistic bug from a bad API response
return <p>Welcome, {user.name.toUpperCase()}</p>;
}
export default function App() {
return (
<div>
<h1>Dashboard</h1>
<ErrorBoundary>
<BuggyProfile user={null} />
</ErrorBoundary>
<p>This footer still renders normally.</p>
</div>
);
}
Renders "Dashboard", then "Something went wrong with this section." in place of the crashing BuggyProfile component, and still renders the footer paragraph normally — the error is contained instead of blanking the whole page.
Common Mistakes
- Trying to write an error boundary as a function component with hooks — React currently has no hook equivalent for getDerivedStateFromError/componentDidCatch.
- Expecting an error boundary to catch errors thrown inside an onClick or other event handler — those need a manual try/catch.
- Wrapping the entire application in a single top-level error boundary only, so any error still blanks the whole UI instead of just the failing section.
- Forgetting to reset the boundary's state (e.g. via a "try again" button or a key change) so users get stuck on the fallback UI permanently after a transient error.
Exercise
Do this in src/App.jsx of a Vite + React project. Write a class component ErrorBoundary that shows "Something went wrong" when a component inside it throws while rendering, and logs the error. Wrap Broken, which always throws, in it.
Expected result: the page shows the heading and "Something went wrong" in place of the broken component, instead of going blank.
import { Component } from "react";
// TODO: class ErrorBoundary extends Component
// - state { hasError: false }
// - static getDerivedStateFromError
// - componentDidCatch that logs the error
// - render the fallback or the children
function Broken() {
throw new Error("This component failed");
}
export default function App() {
return (
<div>
<h1>Dashboard</h1>
{/* TODO: render Broken inside the ErrorBoundary */}
</div>
);
}
Show hints
static getDerivedStateFromError()returns the new state, such as{ hasError: true }, so that the next render shows the fallback.componentDidCatch(error, info)is the place to log or report the error.
Show solution
import { Component } from "react";
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error("Caught by ErrorBoundary:", error, info.componentStack);
}
render() {
if (this.state.hasError) {
return <p>Something went wrong</p>;
}
return this.props.children;
}
}
function Broken() {
throw new Error("This component failed");
}
export default function App() {
return (
<div>
<h1>Dashboard</h1>
<ErrorBoundary>
<Broken />
</ErrorBoundary>
</div>
);
}
Quick quiz
1. What kind of component must an error boundary be?
- A. A function component with a hook
- B. Any component
- C. A class component
- D. A context provider
Show answer
C. There is no hook equivalent of getDerivedStateFromError or componentDidCatch.
2. Which error does an error boundary NOT catch?
- A. An error thrown inside an event handler
- B. An error thrown while a child renders
- C. An error in a child's constructor
- D. An error in a child's lifecycle method
Show answer
A. Errors in event handlers and asynchronous code are handled with try and catch.
3. What does the user see when a rendering error is not caught by any boundary?
- A. An alert
- B. The whole React tree is removed, leaving a blank page
- C. Only the broken component disappears
- D. The previous page
Show answer
B. React prefers showing nothing to showing a corrupted interface.
Interview questions
What is an error boundary?
Show answer
An error boundary is a class component that catches JavaScript errors thrown while rendering its child tree, in their lifecycle methods and constructors, and renders a fallback interface in place of the part that failed. It implements static getDerivedStateFromError, to switch to the fallback, and componentDidCatch, to report the error. Without one, a single rendering error unmounts the entire application.
What do error boundaries not catch, and how are those errors handled?
Show answer
They do not catch errors in event handlers, in asynchronous code such as timers and promise callbacks, during server-side rendering, or thrown by the boundary itself. Event handlers and async functions use ordinary try and catch, storing the error in state so that it can be shown. Boundaries are usually placed at several levels: one around the whole application, and others around independent sections so that one failure does not take down the rest.
Key Points to Remember
- Error boundaries catch rendering, lifecycle, and constructor errors in their child tree and show a fallback UI instead of crashing the app.
- They must be class components today, because getDerivedStateFromError and componentDidCatch have no hook equivalent yet.
- They do not catch errors in event handlers or asynchronous callbacks — those still need try/catch.
- Placing multiple, narrowly-scoped error boundaries around independent sections limits the blast radius of any single failure.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Shows a live preview of plain HTML, CSS and JavaScript. JSX needs a React project on your computer. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
These examples need a project environment on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering