Skip to content

Commit 6300a9f

Browse files
authored
Stabilize client-side onError (#14546)
* Stabilize client-side onError * Docs
1 parent f450f1f commit 6300a9f

File tree

10 files changed

+148
-63
lines changed

10 files changed

+148
-63
lines changed

.changeset/small-flowers-drive.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": minor
3+
---
4+
5+
Stabilize `<HydratedRouter onError>`/`<RouterProvider onError>`

docs/how-to/error-boundary.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ title: Error Boundaries
1111

1212
To avoid rendering an empty page to users, route modules will automatically catch errors in your code and render the closest `ErrorBoundary`.
1313

14-
Error boundaries are not intended for error reporting or rendering form validation errors. Please see [Form Validation](./form-validation) and [Error Reporting](./error-reporting) instead.
14+
Error boundaries are not intended for rendering form validation errors or error reporting. Please see [Form Validation](./form-validation) and [Error Reporting](./error-reporting) instead.
1515

1616
## 1. Add a root error boundary
1717

docs/how-to/error-reporting.md

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,28 @@ title: Error Reporting
44

55
# Error Reporting
66

7-
[MODES: framework]
7+
[MODES: framework,data]
88

99
<br/>
1010
<br/>
1111

12-
React Router catches errors in your route modules and sends them to [error boundaries](./error-boundary) to prevent blank pages when errors occur. However, ErrorBoundary isn't sufficient for logging and reporting errors. To access these caught errors, use the handleError export of the server entry module.
12+
React Router catches errors in your route modules and sends them to [error boundaries](./error-boundary) to prevent blank pages when errors occur. However, `ErrorBoundary` isn't sufficient for logging and reporting errors.
1313

14-
## 1. Reveal the server entry
14+
## Server Errors
1515

16-
If you don't see `entry.server.tsx` in your app directory, you're using a default entry. Reveal it with this cli command:
16+
[modes: framework]
17+
18+
To access these caught errors on the server, use the `handleError` export of the server entry module.
19+
20+
### 1. Reveal the server entry
21+
22+
If you don't see [`entry.server.tsx`][entryserver] in your app directory, you're using a default entry. Reveal it with this cli command:
1723

1824
```shellscript nonumber
19-
react-router reveal
25+
react-router reveal entry.server
2026
```
2127

22-
## 2. Export your error handler
28+
### 2. Export your error handler
2329

2430
This function is called whenever React Router catches an error in your application on the server.
2531

@@ -39,3 +45,75 @@ export const handleError: HandleErrorFunction = (
3945
}
4046
};
4147
```
48+
49+
## Client Errors
50+
51+
To access these caught errors on the client, use the `onError` prop on your [`HydratedRouter`][hydratedrouter] or [`RouterProvider`][routerprovider] component.
52+
53+
### Framework Mode
54+
55+
[modes: framework]
56+
57+
#### 1. Reveal the client entry
58+
59+
If you don't see [`entry.client.tsx`][entryclient] in your app directory, you're using a default entry. Reveal it with this cli command:
60+
61+
```shellscript nonumber
62+
react-router reveal entry.client
63+
```
64+
65+
#### 2. Add your error handler
66+
67+
This function is called whenever React Router catches an error in your application on the client.
68+
69+
```tsx filename=entry.client.tsx
70+
import { type ClientOnErrorFunction } from "react-router";
71+
72+
const onError: ClientOnErrorFunction = (
73+
error,
74+
{ location, params, unstable_pattern, errorInfo },
75+
) => {
76+
myReportError(error, location, errorInfo);
77+
78+
// make sure to still log the error so you can see it
79+
console.error(error, errorInfo);
80+
};
81+
82+
startTransition(() => {
83+
hydrateRoot(
84+
document,
85+
<StrictMode>
86+
<HydratedRouter onError={onError} />
87+
</StrictMode>,
88+
);
89+
});
90+
```
91+
92+
### Data Mode
93+
94+
[modes: data]
95+
96+
This function is called whenever React Router catches an error in your application on the client.
97+
98+
```tsx
99+
import { type ClientOnErrorFunction } from "react-router";
100+
101+
const onError: ClientOnErrorFunction = (
102+
error,
103+
{ location, params, unstable_pattern, errorInfo },
104+
) => {
105+
myReportError(error, location, errorInfo);
106+
107+
// make sure to still log the error so you can see it
108+
console.error(error, errorInfo);
109+
};
110+
111+
function App() {
112+
return <RouterProvider onError={onError} />;
113+
}
114+
```
115+
116+
[entryserver]: ../api/framework-conventions/entry.server.tsx
117+
[entryclient]: ../api/framework-conventions/entry.client.tsx
118+
[hydratedrouter]: ../api//framework-routers/HydratedRouter
119+
[routerprovider]: ../api/data-routers/RouterProvider

integration/browser-entry-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ test("allows users to pass an onError function to HydratedRouter", async ({
146146
document,
147147
<StrictMode>
148148
<HydratedRouter
149-
unstable_onError={(error, errorInfo) => {
149+
onError={(error, errorInfo) => {
150150
console.log(error.message, JSON.stringify(errorInfo))
151151
}}
152152
/>

packages/react-router/__tests__/dom/client-on-error-test.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe(`handleError`, () => {
4444
]);
4545

4646
let { container } = render(
47-
<RouterProvider router={router} unstable_onError={spy} />,
47+
<RouterProvider router={router} onError={spy} />,
4848
);
4949
await waitFor(() => screen.getByText("lazy error!"));
5050

@@ -75,7 +75,7 @@ describe(`handleError`, () => {
7575
},
7676
]);
7777

78-
render(<RouterProvider router={router} unstable_onError={spy} />);
78+
render(<RouterProvider router={router} onError={spy} />);
7979

8080
await waitFor(() => screen.getByText("Error:middleware error!"));
8181

@@ -104,7 +104,7 @@ describe(`handleError`, () => {
104104
},
105105
]);
106106

107-
render(<RouterProvider router={router} unstable_onError={spy} />);
107+
render(<RouterProvider router={router} onError={spy} />);
108108

109109
await waitFor(() => screen.getByText("Error:loader error!"));
110110

@@ -134,7 +134,7 @@ describe(`handleError`, () => {
134134
]);
135135

136136
let { container } = render(
137-
<RouterProvider router={router} unstable_onError={spy} />,
137+
<RouterProvider router={router} onError={spy} />,
138138
);
139139

140140
await act(() => router.navigate("/page"));
@@ -170,7 +170,7 @@ describe(`handleError`, () => {
170170
]);
171171

172172
let { container } = render(
173-
<RouterProvider router={router} unstable_onError={spy} />,
173+
<RouterProvider router={router} onError={spy} />,
174174
);
175175

176176
await act(() => router.navigate("/page"));
@@ -202,7 +202,7 @@ describe(`handleError`, () => {
202202
]);
203203

204204
let { container } = render(
205-
<RouterProvider router={router} unstable_onError={spy} />,
205+
<RouterProvider router={router} onError={spy} />,
206206
);
207207

208208
await act(() => router.navigate("/page"));
@@ -234,7 +234,7 @@ describe(`handleError`, () => {
234234
]);
235235

236236
let { container } = render(
237-
<RouterProvider router={router} unstable_onError={spy} />,
237+
<RouterProvider router={router} onError={spy} />,
238238
);
239239

240240
await act(() =>
@@ -269,7 +269,7 @@ describe(`handleError`, () => {
269269
]);
270270

271271
let { container } = render(
272-
<RouterProvider router={router} unstable_onError={spy} />,
272+
<RouterProvider router={router} onError={spy} />,
273273
);
274274

275275
await act(() => router.fetch("key", "0", "/fetch"));
@@ -299,7 +299,7 @@ describe(`handleError`, () => {
299299
]);
300300

301301
let { container } = render(
302-
<RouterProvider router={router} unstable_onError={spy} />,
302+
<RouterProvider router={router} onError={spy} />,
303303
);
304304

305305
await act(() =>
@@ -335,7 +335,7 @@ describe(`handleError`, () => {
335335
]);
336336

337337
let { container } = render(
338-
<RouterProvider router={router} unstable_onError={spy} />,
338+
<RouterProvider router={router} onError={spy} />,
339339
);
340340

341341
await act(() => router.navigate("/page"));
@@ -380,7 +380,7 @@ describe(`handleError`, () => {
380380
]);
381381

382382
let { container } = render(
383-
<RouterProvider router={router} unstable_onError={spy} />,
383+
<RouterProvider router={router} onError={spy} />,
384384
);
385385

386386
await act(() => router.navigate("/page"));
@@ -429,7 +429,7 @@ describe(`handleError`, () => {
429429
}
430430

431431
let { container } = render(
432-
<RouterProvider router={router} unstable_onError={spy} />,
432+
<RouterProvider router={router} onError={spy} />,
433433
);
434434

435435
await act(() => router.navigate("/page"));
@@ -484,7 +484,7 @@ describe(`handleError`, () => {
484484
}
485485

486486
let { container } = render(
487-
<RouterProvider router={router} unstable_onError={spy} />,
487+
<RouterProvider router={router} onError={spy} />,
488488
);
489489

490490
await act(() => router.navigate("/page"));
@@ -540,7 +540,7 @@ describe(`handleError`, () => {
540540
]);
541541

542542
let { container } = render(
543-
<RouterProvider router={router} unstable_onError={spy} />,
543+
<RouterProvider router={router} onError={spy} />,
544544
);
545545

546546
await act(() => router.navigate("/page"));
@@ -591,7 +591,7 @@ describe(`handleError`, () => {
591591
]);
592592

593593
let { container } = render(
594-
<RouterProvider router={router} unstable_onError={spy} />,
594+
<RouterProvider router={router} onError={spy} />,
595595
);
596596

597597
await act(() => router.navigate("/page"));

packages/react-router/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export { AwaitContextProvider as UNSAFE_AwaitContextProvider } from "./lib/conte
103103
export type {
104104
AwaitProps,
105105
IndexRouteProps,
106-
unstable_ClientOnErrorFunction,
106+
ClientOnErrorFunction,
107107
LayoutRouteProps,
108108
MemoryRouterOpts,
109109
MemoryRouterProps,

0 commit comments

Comments
 (0)