Every React developer fears the "White Screen of Death". When a single component throws an unhandled error, React unmounts the entire component tree, leaving the user staring at a blank page.
To fix this, we use Error Boundaries. But there is a hidden trap that catches even senior teams: if you catch an error locally to show a fallback UI, the error stops bubbling up. Suddenly, your app is failing in production, users are seeing a generic "Something went wrong" message, and your Sentry dashboard is completely silent. You just traded a UX nightmare for an observability nightmare.
The Traditional (and Messy) Approach
When we write custom Error Boundaries from scratch, we usually end up mixing UI logic with telemetry logic.
// The typical, messy approach
class CustomErrorBoundary extends React.Component {
state = { hasError: false };
componentDidCatch(error, errorInfo) {
this.setState({ hasError: true });
// If a dev forgets to add this line, Sentry goes blind!
Sentry.captureException(error, { extra: errorInfo });
}
render() {
if (this.state.hasError) {
return <div className="ugly-red-box">Something went wrong.</div>;
}
return this.props.children;
}
}
This scales poorly. Developers often forget to wire up the logging service when quickly wrapping a new component, and you end up rewriting fallback UIs (toasts, banners, modals) for every different part of your app.
The Solution: Separation of Concerns
Good Developer Experience (DX) is about combining the right tools for the right jobs. Sentry is incredible at backend telemetry and tracking bugs. But it shouldn't be responsible for your frontend UI.
To solve this, I built CogniCatch, an open-source React wrapper that standardizes Graceful UI Degradation. Instead of a blank screen, it replaces the broken component with standardized, empathetic UI notifications.
In my latest release (v1.1.6), I introduced a fail-safe onError callback specifically designed to solve the "swallowed error" problem.
Here is how you pair CogniCatch with Sentry to get the perfect architecture:
import * as Sentry from "@sentry/react";
import { AdaptiveErrorBoundary } from "@cognicatch/react";
export function CheckoutFlow() {
return (
<AdaptiveErrorBoundary
mode="manual"
severity="medium"
title="Payment Failed"
description="We couldn't process your card right now. Please try again."
// The magic happens here:
// The UI stays up, and Sentry gets the log silently.
onError={(error, errorInfo) => {
Sentry.captureException(error, { extra: errorInfo });
}}
>
<PaymentForm />
</AdaptiveErrorBoundary>
);
}
The Double Benefit
By decoupling the UI degradation from the telemetry, everybody wins:
For the User (Perfect UX): Instead of a broken layout, they see a clean, localized banner (or toast) letting them know the payment failed. The rest of the application (sidebar, header, navigation) remains perfectly intact and interactive.
For the Developer (Perfect DX): The onError callback runs safely in the background inside a hardened try/catch block. Sentry immediately lights up in your Slack channel with the exact stack trace and component tree. No silent failures.
Try it out
I built CogniCatch open-source because I was tired of writing endless switch statements to map API errors and polluting my components with logger logic.
If you want to stop losing telemetry while keeping your users happy, check out the repository and give it a try.
⭐ Star CogniCatch on GitHub
npm install @cognicatch/react
Let Sentry handle the logs. Let CogniCatch handle the interface.
(Let me know in the comments how your team currently handles React fallbacks alongside your observability stack!)
Top comments (0)