Error Boundaries and Suspense
Error Boundaries and Suspense
A report widget may fail to render while the rest of the school dashboard is still useful. Another widget may simply be waiting for its code to arrive. Calling both situations “loading” hides whether students should wait or seek recovery.
An Error Boundary supplies fallback UI for supported rendering errors in its descendant tree. Suspense supplies fallback UI while supported descendants suspend. Failure and pending work are different states with different recovery needs.
Separate outcomes
Distinguish failure from supported pending work.
Place a boundary
Protect a useful part of the interface.
Handle other errors
Manage event and ordinary request failures explicitly.
Follow the user’s interaction
Region
The report has its own boundary.
Pending
Supported suspension can show a loading fallback.
Failed
A render error can show a failure fallback.
A boundary describes a region of responsibility
A report failure should not necessarily replace the entire dashboard. Place a boundary around a coherent region and choose a fallback that names what is unavailable. React’s built-in Error Boundary pattern uses a class with getDerivedStateFromError; regular function components can still be its children.
Suspense responds to supported suspension, such as lazy component loading or a framework’s compatible data source. It does not observe every asynchronous operation. React explains Error Boundaries, while the Suspense reference describes the separate pending-work mechanism.
A healthy report inside both boundaries
Initially Report renders Maya: 75 normally. The Suspense fallback is not displayed because this report does not suspend. Selecting Try damaged report makes Report throw while rendering; the surrounding Error Boundary replaces that region with Report cannot be displayed. The heading and button remain outside it.
This is a deliberate render-failure demonstration, so development tools may report the thrown error even though the boundary displays fallback UI. The boundary does not fix the record or retry it automatically. Its failed state remains set until a deliberate reset or remount.
React · JavaScript with JSX
import React, { Component, Suspense, useState } from 'react';
class ReportBoundary extends Component {
constructor(props) { super(props); this.state = { failed: false }; }
static getDerivedStateFromError() { return { failed: true }; }
render() {
return this.state.failed ? <p role="alert">Report cannot be displayed</p> : this.props.children;
}
}
function Report({ broken }) {
if (broken) throw new Error('Invalid report record');
return <p>Maya: 75</p>;
}
export default function App() {
const [broken, setBroken] = useState(false);
return <main><h1>School reports</h1>
<button onClick={() => setBroken(true)}>Try damaged report</button>
<ReportBoundary><Suspense fallback={<p>Loading report</p>}>
<Report broken={broken} />
</Suspense></ReportBoundary>
</main>;
}Expected screen and behavior
Initially: School reports, Try damaged report and Maya: 75.
A damaged render displays Report cannot be displayed; development error reporting is expected.The failure happens during rendering
React · the boundary and throwing child
import React, { Component, Suspense, useState } from 'react';
class ReportBoundary extends Component {
constructor(props) { super(props); this.state = { failed: false }; }
static getDerivedStateFromError() { return { failed: true }; }
render() {
return this.state.failed ? <p role="alert">Report cannot be displayed</p> : this.props.children;
}
}
function Report({ broken }) {
if (broken) throw new Error('Invalid report record');
return <p>Maya: 75</p>;
}
export default function App() {
const [broken, setBroken] = useState(false);
return <main><h1>School reports</h1>
<button onClick={() => setBroken(true)}>Try damaged report</button>
<ReportBoundary><Suspense fallback={<p>Loading report</p>}>
<Report broken={broken} />
</Suspense></ReportBoundary>
</main>;
}The button handler only updates broken. During the subsequent render, Report throws. That location matters: a surrounding Error Boundary can handle this supported descendant render error. A try/catch around setBroken would not surround the later React rendering work.
A boundary cannot catch its own rendering failure. Keep fallback code simple so it does not depend on the damaged record. Production monitoring can use the appropriate reporting integration, but avoid sending private report contents merely because a diagnostic message is convenient.
Real local lazy loading, with an artificial wait
This complete alternative App.jsx uses React.lazy and Suspense. The loader returns a promise for a module-shaped object with a default component. A local 300-millisecond timer deliberately delays that promise; it performs no network request and does not measure deployment performance. In a mounted browser it first shows Loading report, then Maya: 75.
In a real code-splitting setup, lazy commonly receives a dynamic import produced by the bundler. The fallback belongs to the boundary, while the loader belongs outside App so each render does not create a new component type. Server rendering needs the server’s compatible streaming behavior; a browser demonstration does not establish those server capabilities.
React · JavaScript with JSX
import React, { lazy, Suspense } from 'react';
const Report = lazy(() => new Promise(resolve => {
setTimeout(() => resolve({
default: function Report() { return <p>Maya: 75</p>; }
}), 300);
}));
export default function App() {
return <main><h1>School reports</h1>
<Suspense fallback={<p>Loading report</p>}><Report /></Suspense>
</main>;
}Expected screen and behavior
Initially: School reports · Loading report
After the local delay: School reports · Maya: 75Worked practice: change the failure explanation
Give the failure fallback a message that names the report and advises a safe next step. Keep it independent of the broken record. The complete answer below changes only the fallback content; initially the healthy report still appears.
After an intentional damaged render, the explanation is Report unavailable. Contact the school office. Do not add a Retry button that has no reset behavior: a useful action must actually clear the relevant failure and attempt an operation that can succeed.
React · JavaScript with JSX
import React, { Component, Suspense, useState } from 'react';
class ReportBoundary extends Component {
constructor(props) { super(props); this.state = { failed: false }; }
static getDerivedStateFromError() { return { failed: true }; }
render() {
return this.state.failed ? <p role="alert">Report unavailable. Contact the school office.</p> : this.props.children;
}
}
function Report({ broken }) {
if (broken) throw new Error('Invalid report record');
return <p>Maya: 75</p>;
}
export default function App() {
const [broken, setBroken] = useState(false);
return <main><h1>School reports</h1>
<button onClick={() => setBroken(true)}>Try damaged report</button>
<ReportBoundary><Suspense fallback={<p>Loading report</p>}>
<Report broken={broken} />
</Suspense></ReportBoundary>
</main>;
}Expected screen and behavior
Initially: Maya: 75.
The damaged render displays Report unavailable. Contact the school office.Not every failure belongs to these boundaries
Ordinary event-handler errors and asynchronous callback errors are not generally caught by an Error Boundary. Validate forms and handle request rejection in their owning event or data layer. A missing student can often be an expected UI state rather than an exception that replaces the whole region.
Fetching in an Effect does not by itself activate Suspense. Give that design explicit loading and error state, or use an actual compatible framework or data integration. Keep the existing useful screen visible when appropriate and avoid making every small pending operation blank the entire application.
Check the fallback
Apply error ownership
Show whether work is waiting or has failed
An Error Boundary isolates supported render failures; Suspense coordinates supported suspension. Place them around meaningful regions and provide truthful fallbacks. Handle expected missing data, event errors and ordinary asynchronous failures at their actual owner.
Connect these choices with explicit data-loading states and investigating the underlying failure.
Reaching the recap and completing the quizzes are separate milestones.