Chapter 2 of 4
What Boundaries Catch
The four categories they miss, and what to do about each.
A boundary catches errors thrown during rendering, in lifecycle methods, and in constructors of the components below it. Everything else escapes.
Not caught: event handlers
Handlers run outside the render cycle, so React cannot intercept them. Use a normal try/catch.
function SaveButton() {
const [error, setError] = useState(null);
async function handleClick() {
try {
await save();
} catch (err) {
setError(err);
}
}
if (error) return <p role="alert">Save failed.</p>;
return <button onClick={handleClick}>Save</button>;
}Not caught: asynchronous code
A rejected promise or an error inside setTimeout happens long after rendering finished. To route it to a boundary, catch it and set state that throws during the next render.
const [error, setError] = useState(null);
if (error) throw error; // now a boundary can catch it
useEffect(() => {
fetchData().catch(setError);
}, []);Not caught: server rendering
Boundaries do not catch errors during server-side rendering. Frameworks provide their own error handling for that phase - error.js in Next.js, for example.
Not caught: errors in the boundary itself
A boundary cannot catch its own errors. If the fallback throws, the error propagates to the next boundary up - so keep fallbacks extremely simple.
// Risky: error.response may be undefined
<p>{error.response.data.message}</p>
// Safe
<p>Something went wrong. Please try again.</p>