Skip to content

Commit b8b01b3

Browse files
committed
Stabilize client-side onError
1 parent a6182f5 commit b8b01b3

File tree

8 files changed

+51
-46
lines changed

8 files changed

+51
-46
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>`

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,

packages/react-router/lib/components.tsx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ class Deferred<T> {
333333
* Function signature for client side error handling for loader/actions errors
334334
* and rendering errors via `componentDidCatch`
335335
*/
336-
export interface unstable_ClientOnErrorFunction {
336+
export interface ClientOnErrorFunction {
337337
(
338338
error: unknown,
339339
info: {
@@ -374,13 +374,13 @@ export interface RouterProviderProps {
374374
* and is only present for render errors.
375375
*
376376
* ```tsx
377-
* <RouterProvider unstable_onError=(error, errorInfo) => {
377+
* <RouterProvider onError=(error, errorInfo) => {
378378
* console.error(error, errorInfo);
379379
* reportToErrorService(error, errorInfo);
380380
* }} />
381381
* ```
382382
*/
383-
unstable_onError?: unstable_ClientOnErrorFunction;
383+
onError?: ClientOnErrorFunction;
384384
/**
385385
* Control whether router state updates are internally wrapped in
386386
* [`React.startTransition`](https://react.dev/reference/react/startTransition).
@@ -429,15 +429,15 @@ export interface RouterProviderProps {
429429
* @mode data
430430
* @param props Props
431431
* @param {RouterProviderProps.flushSync} props.flushSync n/a
432-
* @param {RouterProviderProps.unstable_onError} props.unstable_onError n/a
432+
* @param {RouterProviderProps.onError} props.onError n/a
433433
* @param {RouterProviderProps.router} props.router n/a
434434
* @param {RouterProviderProps.unstable_useTransitions} props.unstable_useTransitions n/a
435435
* @returns React element for the rendered router
436436
*/
437437
export function RouterProvider({
438438
router,
439439
flushSync: reactDomFlushSyncImpl,
440-
unstable_onError,
440+
onError,
441441
unstable_useTransitions,
442442
}: RouterProviderProps): React.ReactElement {
443443
let [_state, setStateImpl] = React.useState(router.state);
@@ -461,9 +461,9 @@ export function RouterProvider({
461461
{ deletedFetchers, newErrors, flushSync, viewTransitionOpts },
462462
) => {
463463
// Send router errors through onError
464-
if (newErrors && unstable_onError) {
464+
if (newErrors && onError) {
465465
Object.values(newErrors).forEach((error) =>
466-
unstable_onError(error, {
466+
onError(error, {
467467
location: newState.location,
468468
params: newState.matches[0]?.params ?? {},
469469
unstable_pattern: getRoutePattern(newState.matches),
@@ -583,7 +583,7 @@ export function RouterProvider({
583583
renderDfd,
584584
unstable_useTransitions,
585585
setOptimisticState,
586-
unstable_onError,
586+
onError,
587587
],
588588
);
589589

@@ -689,9 +689,9 @@ export function RouterProvider({
689689
navigator,
690690
static: false,
691691
basename,
692-
unstable_onError,
692+
onError,
693693
}),
694-
[router, navigator, basename, unstable_onError],
694+
[router, navigator, basename, onError],
695695
);
696696

697697
// The fragment and {null} here are important! We need them to keep React 18's
@@ -717,7 +717,7 @@ export function RouterProvider({
717717
routes={router.routes}
718718
future={router.future}
719719
state={state}
720-
unstable_onError={unstable_onError}
720+
onError={onError}
721721
/>
722722
</Router>
723723
</ViewTransitionContext.Provider>
@@ -763,14 +763,14 @@ function DataRoutes({
763763
routes,
764764
future,
765765
state,
766-
unstable_onError,
766+
onError,
767767
}: {
768768
routes: DataRouteObject[];
769769
future: DataRouter["future"];
770770
state: RouterState;
771-
unstable_onError: unstable_ClientOnErrorFunction | undefined;
771+
onError: ClientOnErrorFunction | undefined;
772772
}): React.ReactElement | null {
773-
return useRoutesImpl(routes, undefined, state, unstable_onError, future);
773+
return useRoutesImpl(routes, undefined, state, onError, future);
774774
}
775775

776776
/**
@@ -1599,10 +1599,10 @@ export function Await<Resolve>({
15991599
(error: unknown, errorInfo?: React.ErrorInfo) => {
16001600
if (
16011601
dataRouterContext &&
1602-
dataRouterContext.unstable_onError &&
1602+
dataRouterContext.onError &&
16031603
dataRouterStateContext
16041604
) {
1605-
dataRouterContext.unstable_onError(error, {
1605+
dataRouterContext.onError(error, {
16061606
location: dataRouterStateContext.location,
16071607
params: dataRouterStateContext.matches[0]?.params || {},
16081608
unstable_pattern: getRoutePattern(dataRouterStateContext.matches),

packages/react-router/lib/context.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from "react";
2-
import type { unstable_ClientOnErrorFunction } from "./components";
2+
import type { ClientOnErrorFunction } from "./components";
33
import type {
44
History,
55
Location,
@@ -92,7 +92,7 @@ export interface DataRouterContextObject
9292
extends Omit<NavigationContextObject, "future" | "unstable_useTransitions"> {
9393
router: Router;
9494
staticContext?: StaticHandlerContext;
95-
unstable_onError?: unstable_ClientOnErrorFunction;
95+
onError?: ClientOnErrorFunction;
9696
}
9797

9898
export const DataRouterContext =

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {
66
DataRouter,
77
HydrationState,
88
RouterInit,
9-
unstable_ClientOnErrorFunction,
9+
ClientOnErrorFunction,
1010
} from "react-router";
1111
import {
1212
UNSAFE_getHydrationData as getHydrationData,
@@ -291,13 +291,13 @@ export interface HydratedRouterProps {
291291
* and is only present for render errors.
292292
*
293293
* ```tsx
294-
* <HydratedRouter unstable_onError={(error, errorInfo) => {
294+
* <HydratedRouter onError={(error, errorInfo) => {
295295
* console.error(error, errorInfo);
296296
* reportToErrorService(error, errorInfo);
297297
* }} />
298298
* ```
299299
*/
300-
unstable_onError?: unstable_ClientOnErrorFunction;
300+
onError?: ClientOnErrorFunction;
301301
/**
302302
* Control whether router state updates are internally wrapped in
303303
* [`React.startTransition`](https://react.dev/reference/react/startTransition).
@@ -328,7 +328,7 @@ export interface HydratedRouterProps {
328328
* @mode framework
329329
* @param props Props
330330
* @param {dom.HydratedRouterProps.getContext} props.getContext n/a
331-
* @param {dom.HydratedRouterProps.unstable_onError} props.unstable_onError n/a
331+
* @param {dom.HydratedRouterProps.onError} props.onError n/a
332332
* @returns A React element that represents the hydrated application.
333333
*/
334334
export function HydratedRouter(props: HydratedRouterProps) {
@@ -424,7 +424,7 @@ export function HydratedRouter(props: HydratedRouterProps) {
424424
<RouterProvider
425425
router={router}
426426
unstable_useTransitions={props.unstable_useTransitions}
427-
unstable_onError={props.unstable_onError}
427+
onError={props.onError}
428428
/>
429429
</RemixErrorBoundary>
430430
</FrameworkContext.Provider>

packages/react-router/lib/hooks.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ import type {
5656
GetLoaderData,
5757
SerializeFrom,
5858
} from "./types/route-data";
59-
import type { unstable_ClientOnErrorFunction } from "./components";
59+
import type { ClientOnErrorFunction } from "./components";
6060
import type { RouteModules } from "./types/register";
6161

6262
/**
@@ -757,7 +757,7 @@ export function useRoutesImpl(
757757
routes: RouteObject[],
758758
locationArg?: Partial<Location> | string,
759759
dataRouterState?: DataRouter["state"],
760-
unstable_onError?: unstable_ClientOnErrorFunction,
760+
onError?: ClientOnErrorFunction,
761761
future?: DataRouter["future"],
762762
): React.ReactElement | null {
763763
invariant(
@@ -911,7 +911,7 @@ export function useRoutesImpl(
911911
),
912912
parentMatches,
913913
dataRouterState,
914-
unstable_onError,
914+
onError,
915915
future,
916916
);
917917

@@ -1106,7 +1106,7 @@ export function _renderMatches(
11061106
matches: RouteMatch[] | null,
11071107
parentMatches: RouteMatch[] = [],
11081108
dataRouterState: DataRouter["state"] | null = null,
1109-
unstable_onError: unstable_ClientOnErrorFunction | null = null,
1109+
onErrorHandler: ClientOnErrorFunction | null = null,
11101110
future: DataRouter["future"] | null = null,
11111111
): React.ReactElement | null {
11121112
if (matches == null) {
@@ -1190,9 +1190,9 @@ export function _renderMatches(
11901190
}
11911191

11921192
let onError =
1193-
dataRouterState && unstable_onError
1193+
dataRouterState && onErrorHandler
11941194
? (error: unknown, errorInfo?: React.ErrorInfo) => {
1195-
unstable_onError(error, {
1195+
onErrorHandler(error, {
11961196
location: dataRouterState.location,
11971197
params: dataRouterState.matches?.[0]?.params ?? {},
11981198
unstable_pattern: getRoutePattern(dataRouterState.matches),

0 commit comments

Comments
 (0)