Skip to content

Commit 19ca284

Browse files
committed
Refactor how router errors are reported to avoid strict mode issues
1 parent 25d4741 commit 19ca284

File tree

3 files changed

+26
-28
lines changed

3 files changed

+26
-28
lines changed

.changeset/long-brooms-shake.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] Internal refactor to how `unstable_onError` is called by `RouterProvider`

packages/react-router/lib/components.tsx

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -542,31 +542,22 @@ export function RouterProvider({
542542
nextLocation: Location;
543543
}>();
544544
let fetcherData = React.useRef<Map<string, any>>(new Map());
545-
let logErrorsAndSetState = React.useCallback(
546-
(newState: RouterState) => {
547-
setStateImpl((prevState) => {
548-
// Send loader/action errors through handleError
549-
if (newState.errors && unstable_onError) {
550-
Object.entries(newState.errors).forEach(([routeId, error]) => {
551-
if (prevState.errors?.[routeId] !== error) {
552-
unstable_onError(error, {
553-
location: newState.location,
554-
params: newState.matches[0]?.params ?? {},
555-
});
556-
}
557-
});
558-
}
559-
return newState;
560-
});
561-
},
562-
[unstable_onError],
563-
);
564545

565546
let setState = React.useCallback<RouterSubscriber>(
566547
(
567548
newState: RouterState,
568-
{ deletedFetchers, flushSync, viewTransitionOpts },
549+
{ deletedFetchers, newErrors, flushSync, viewTransitionOpts },
569550
) => {
551+
// Send router errors through onError
552+
if (newErrors && unstable_onError) {
553+
Object.values(newErrors).forEach((error) =>
554+
unstable_onError(error, {
555+
location: newState.location,
556+
params: newState.matches[0]?.params ?? {},
557+
}),
558+
);
559+
}
560+
570561
newState.fetchers.forEach((fetcher, key) => {
571562
if (fetcher.data !== undefined) {
572563
fetcherData.current.set(key, fetcher.data);
@@ -600,9 +591,9 @@ export function RouterProvider({
600591
// just update and be done with it
601592
if (!viewTransitionOpts || !isViewTransitionAvailable) {
602593
if (reactDomFlushSyncImpl && flushSync) {
603-
reactDomFlushSyncImpl(() => logErrorsAndSetState(newState));
594+
reactDomFlushSyncImpl(() => setStateImpl(newState));
604595
} else {
605-
React.startTransition(() => logErrorsAndSetState(newState));
596+
React.startTransition(() => setStateImpl(newState));
606597
}
607598
return;
608599
}
@@ -613,7 +604,7 @@ export function RouterProvider({
613604
reactDomFlushSyncImpl(() => {
614605
// Cancel any pending transitions
615606
if (transition) {
616-
renderDfd && renderDfd.resolve();
607+
renderDfd?.resolve();
617608
transition.skipTransition();
618609
}
619610
setVtContext({
@@ -626,7 +617,7 @@ export function RouterProvider({
626617

627618
// Update the DOM
628619
let t = router.window!.document.startViewTransition(() => {
629-
reactDomFlushSyncImpl(() => logErrorsAndSetState(newState));
620+
reactDomFlushSyncImpl(() => setStateImpl(newState));
630621
});
631622

632623
// Clean up after the animation completes
@@ -647,7 +638,7 @@ export function RouterProvider({
647638
if (transition) {
648639
// Interrupting an in-progress transition, cancel and let everything flush
649640
// out, and then kick off a new transition from the interruption state
650-
renderDfd && renderDfd.resolve();
641+
renderDfd?.resolve();
651642
transition.skipTransition();
652643
setInterruption({
653644
state: newState,
@@ -670,7 +661,7 @@ export function RouterProvider({
670661
reactDomFlushSyncImpl,
671662
transition,
672663
renderDfd,
673-
logErrorsAndSetState,
664+
unstable_onError,
674665
],
675666
);
676667

@@ -694,7 +685,7 @@ export function RouterProvider({
694685
let newState = pendingState;
695686
let renderPromise = renderDfd.promise;
696687
let transition = router.window.document.startViewTransition(async () => {
697-
React.startTransition(() => logErrorsAndSetState(newState));
688+
React.startTransition(() => setStateImpl(newState));
698689
await renderPromise;
699690
});
700691
transition.finished.finally(() => {
@@ -705,7 +696,7 @@ export function RouterProvider({
705696
});
706697
setTransition(transition);
707698
}
708-
}, [pendingState, renderDfd, router.window, logErrorsAndSetState]);
699+
}, [pendingState, renderDfd, router.window]);
709700

710701
// When the new location finally renders and is committed to the DOM, this
711702
// effect will run to resolve the transition

packages/react-router/lib/router/router.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,7 @@ export interface RouterSubscriber {
489489
state: RouterState,
490490
opts: {
491491
deletedFetchers: string[];
492+
newErrors: RouteData | null;
492493
viewTransitionOpts?: ViewTransitionOpts;
493494
flushSync: boolean;
494495
},
@@ -1292,6 +1293,7 @@ export function createRouter(init: RouterInit): Router {
12921293
[...subscribers].forEach((subscriber) =>
12931294
subscriber(state, {
12941295
deletedFetchers: unmountedFetchers,
1296+
newErrors: newState.errors ?? null,
12951297
viewTransitionOpts: opts.viewTransitionOpts,
12961298
flushSync: opts.flushSync === true,
12971299
}),

0 commit comments

Comments
 (0)