Skip to content

feat(react-router): add unstable_onError prop to RouterProvider for client side error reporting #14162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Aug 20, 2025
Merged
5 changes: 5 additions & 0 deletions .changeset/cold-geese-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-router": patch
---

[UNSTABLE] Add `<RouterProvider unstable_onError>`/`<HydratedRouter unstable_onError>` prop for client side error reporting
67 changes: 67 additions & 0 deletions integration/browser-entry-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,70 @@ test("allows users to pass a client side context to HydratedRouter", async ({

appFixture.close();
});

test("allows users to pass an onError function to HydratedRouter", async ({
page,
browserName,
}) => {
let fixture = await createFixture({
files: {
"app/entry.client.tsx": js`
import { HydratedRouter } from "react-router/dom";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<HydratedRouter
unstable_onError={(error, errorInfo) => {
console.log(error.message, JSON.stringify(errorInfo))
}}
/>
</StrictMode>
);
});
`,
"app/routes/_index.tsx": js`
import { Link } from "react-router";
export default function Index() {
return <Link to="/page">Go to Page</Link>;
}
`,
"app/routes/page.tsx": js`
export default function Page() {
throw new Error("Render error");
}
export function ErrorBoundary({ error }) {
return <h1 data-error>Error: {error.message}</h1>
}
`,
},
});

let logs: string[] = [];
page.on("console", (msg) => logs.push(msg.text()));

let appFixture = await createAppFixture(fixture);
let app = new PlaywrightFixture(appFixture, page);

await app.goto("/", true);
await page.click('a[href="/page"]');
await page.waitForSelector("[data-error]");

expect(await app.getHtml()).toContain("Error: Render error");
expect(logs.length).toBe(2);
// First one is react logging the error
if (browserName === "firefox") {
expect(logs[0]).toContain("Error");
} else {
expect(logs[0]).toContain("Error: Render error");
}
expect(logs[0]).not.toContain("componentStack");
// Second one is ours
expect(logs[1]).toContain("Render error");
expect(logs[1]).toContain('"componentStack":');

appFixture.close();
});
Loading
Loading