Skip to content

Commit ee38f76

Browse files
committed
Add changeset and wire into HydratedRouter
1 parent 4e1bb14 commit ee38f76

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed

.changeset/cold-geese-turn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
[UNSTABLE] Add support for `unstable_handleError` for client side data routers

integration/browser-entry-test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,64 @@ test("allows users to pass a client side context to HydratedRouter", async ({
129129

130130
appFixture.close();
131131
});
132+
133+
test("allows users to pass a handleError function to HydratedRouter", async ({
134+
page,
135+
}) => {
136+
let fixture = await createFixture({
137+
files: {
138+
"app/entry.client.tsx": js`
139+
import { HydratedRouter } from "react-router/dom";
140+
import { startTransition, StrictMode } from "react";
141+
import { hydrateRoot } from "react-dom/client";
142+
143+
startTransition(() => {
144+
hydrateRoot(
145+
document,
146+
<StrictMode>
147+
<HydratedRouter
148+
unstable_handleError={(error, { location, errorInfo }) => {
149+
console.log(error.message, JSON.stringify(errorInfo), location.pathname)
150+
}}
151+
/>
152+
</StrictMode>
153+
);
154+
});
155+
`,
156+
"app/routes/_index.tsx": js`
157+
import { Link } from "react-router";
158+
export default function Index() {
159+
return <Link to="/page">Go to Page</Link>;
160+
}
161+
`,
162+
"app/routes/page.tsx": js`
163+
export default function Page() {
164+
throw new Error("Render error");
165+
}
166+
export function ErrorBoundary({ error }) {
167+
return <h1 data-error>Error: {error.message}</h1>
168+
}
169+
`,
170+
},
171+
});
172+
173+
let logs: string[] = [];
174+
page.on("console", (msg) => logs.push(msg.text()));
175+
176+
let appFixture = await createAppFixture(fixture);
177+
let app = new PlaywrightFixture(appFixture, page);
178+
await app.goto("/", true);
179+
await page.click('a[href="/page"]');
180+
await page.waitForSelector("[data-error]");
181+
expect(await app.getHtml()).toContain("Error: Render error");
182+
expect(logs.length).toBe(2);
183+
// First one is react logging the error
184+
expect(logs[0]).toContain("Error: Render error");
185+
expect(logs[0]).not.toContain("componentStack");
186+
// Second one is ours
187+
expect(logs[1]).toContain("Render error");
188+
expect(logs[1]).toContain('"componentStack":');
189+
expect(logs[1]).toContain("/page");
190+
191+
appFixture.close();
192+
});

packages/react-router/lib/dom-export/hydrated-router.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ function initSsrInfo(): void {
7777

7878
function createHydratedRouter({
7979
unstable_getContext,
80+
unstable_handleError,
8081
}: {
8182
unstable_getContext?: RouterInit["unstable_getContext"];
83+
unstable_handleError?: RouterInit["unstable_handleError"];
8284
}): DataRouter {
8385
initSsrInfo();
8486

@@ -169,6 +171,7 @@ function createHydratedRouter({
169171
history: createBrowserHistory(),
170172
basename: ssrInfo.context.basename,
171173
unstable_getContext,
174+
unstable_handleError,
172175
hydrationData,
173176
hydrationRouteProperties,
174177
mapRouteProperties,
@@ -222,6 +225,27 @@ export interface HydratedRouterProps {
222225
* functions
223226
*/
224227
unstable_getContext?: RouterInit["unstable_getContext"];
228+
/**
229+
* An error handler function that will be called for any loader/action/render
230+
* errors that are encountered in your application. This is useful for
231+
* logging or reporting errors instead of the `ErrorBoundary` because it's not
232+
* subject to re-rendering and will only run one time per error.
233+
*
234+
* The `errorInfo` parameter is passed along from
235+
* [`componentDidCatch`](https://react.dev/reference/react/Component#componentdidcatch)
236+
* and is only present for render errors.
237+
*
238+
* ```tsx
239+
* <HydratedRouter unstable_handleError={(error, { location, errorInfo }) => {
240+
* console.log(
241+
* `Error at location ${location.pathname}`,
242+
* error,
243+
* errorInfo
244+
* );
245+
* }} />
246+
* ```
247+
*/
248+
unstable_handleError?: RouterInit["unstable_handleError"];
225249
}
226250

227251
/**
@@ -233,12 +257,14 @@ export interface HydratedRouterProps {
233257
* @mode framework
234258
* @param props Props
235259
* @param {dom.HydratedRouterProps.unstable_getContext} props.unstable_getContext n/a
260+
* @param {dom.HydratedRouterProps.unstable_handleError} props.unstable_handleError n/a
236261
* @returns A React element that represents the hydrated application.
237262
*/
238263
export function HydratedRouter(props: HydratedRouterProps) {
239264
if (!router) {
240265
router = createHydratedRouter({
241266
unstable_getContext: props.unstable_getContext,
267+
unstable_handleError: props.unstable_handleError,
242268
});
243269
}
244270

0 commit comments

Comments
 (0)