Get started

Catching render errors (React)

When a component throws, there is no screen left to point at — but there is a message, a stack and a component stack, which is the best evidence a bug report ever carries. BugReportBoundary catches it and hands your fallback the error and a report() function:

tsx
import { BugReportBoundary } from "bugbottle/react";

<BugReportBoundary
  endpoint="/api/feedback"
  fallback={(error, report, sending) => (
    <div role="alert">
      <p>This part of the page stopped working.</p>
      <button onClick={report} disabled={sending}>
        {sending ? "Sending…" : "Tell us what happened"}
      </button>
    </div>
  )}
>
  <Orders />
</BugReportBoundary>;

report() sends a bug report whose message is the error, its stack and the component stack, and resolves true when the endpoint accepted it — so the button can say "sent". Nothing is sent until it is called: a report is a message from a person, and sending one on their behalf without asking is telemetry, which this library is not. onReport(error, id) and onError(error) are there for the surrounding application.

Calling it twice files one report: a second call while the first is in flight gets the same promise, and a call after a successful send does nothing, because it is the same render error either way. A send that failed can be tried again. The third argument, sending, is true while one is in flight, for a button that should say so.

For the errors that reach the root, React 19 takes two handlers, and createRootErrorHandlers builds both:

ts
import { createRoot } from "react-dom/client";
import { createRootErrorHandlers } from "bugbottle/react";

createRoot(node, createRootErrorHandlers({ endpoint: "/api/feedback" })).render(<App />);

These do send by themselves, because there is nobody left to ask. Each distinct error is sent once per dedupeMs (60 000 by default, by the same fingerprint the client and the server share), so a component that throws on every render sends one report rather than a thousand. Say so in your privacy notice, and pass scrub: scrubReport if a message could carry anything personal.

Edit this page on GitHub