Skip to content

Commit 607031f

Browse files
committed
enable RSC transition support on shared router provider
1 parent 7961270 commit 607031f

File tree

14 files changed

+257
-205
lines changed

14 files changed

+257
-205
lines changed

packages/react-router/lib/components.tsx

Lines changed: 76 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -371,144 +371,6 @@ export interface RouterProviderProps {
371371
unstable_transitions?: boolean;
372372
}
373373

374-
function shallowDiff(a: any, b: any) {
375-
if (a === b) {
376-
return false;
377-
}
378-
let aKeys = Object.keys(a);
379-
let bKeys = Object.keys(b);
380-
if (aKeys.length !== bKeys.length) {
381-
return true;
382-
}
383-
for (let key of aKeys) {
384-
if (a[key] !== b[key]) {
385-
return true;
386-
}
387-
}
388-
return false;
389-
}
390-
391-
export function UNSTABLE_TransitionEnabledRouterProvider({
392-
router,
393-
flushSync: reactDomFlushSyncImpl,
394-
unstable_onError,
395-
}: Omit<RouterProviderProps, "unstable_transitions">): React.ReactElement {
396-
let fetcherData = React.useRef<Map<string, any>>(new Map());
397-
let [revalidating, startRevalidation] = React.useTransition();
398-
let [state, setState] = React.useState(router.state);
399-
400-
(router as any).__setPendingRerender = (promise: Promise<() => void>) =>
401-
startRevalidation(
402-
// @ts-expect-error - need react 19 types for this to be async
403-
async () => {
404-
const rerender = await promise;
405-
startRevalidation(() => {
406-
rerender();
407-
});
408-
},
409-
);
410-
411-
let navigator = React.useMemo((): Navigator => {
412-
return {
413-
createHref: router.createHref,
414-
encodeLocation: router.encodeLocation,
415-
go: (n) => router.navigate(n),
416-
push: (to, state, opts) =>
417-
router.navigate(to, {
418-
state,
419-
preventScrollReset: opts?.preventScrollReset,
420-
}),
421-
replace: (to, state, opts) =>
422-
router.navigate(to, {
423-
replace: true,
424-
state,
425-
preventScrollReset: opts?.preventScrollReset,
426-
}),
427-
};
428-
}, [router]);
429-
430-
let basename = router.basename || "/";
431-
432-
let dataRouterContext = React.useMemo(
433-
() => ({
434-
router,
435-
navigator,
436-
static: false,
437-
basename,
438-
unstable_onError,
439-
}),
440-
[router, navigator, basename, unstable_onError],
441-
);
442-
443-
React.useLayoutEffect(() => {
444-
return router.subscribe(
445-
(newState, { deletedFetchers, flushSync, viewTransitionOpts }) => {
446-
newState.fetchers.forEach((fetcher, key) => {
447-
if (fetcher.data !== undefined) {
448-
fetcherData.current.set(key, fetcher.data);
449-
}
450-
});
451-
deletedFetchers.forEach((key) => fetcherData.current.delete(key));
452-
453-
const diff = shallowDiff(state, newState);
454-
455-
if (!diff) return;
456-
457-
if (flushSync) {
458-
if (reactDomFlushSyncImpl) {
459-
reactDomFlushSyncImpl(() => setState(newState));
460-
} else {
461-
setState(newState);
462-
}
463-
} else {
464-
React.startTransition(() => {
465-
setState(newState);
466-
});
467-
}
468-
},
469-
);
470-
}, [router, reactDomFlushSyncImpl, state]);
471-
472-
// The fragment and {null} here are important! We need them to keep React 18's
473-
// useId happy when we are server-rendering since we may have a <script> here
474-
// containing the hydrated server-side staticContext (from StaticRouterProvider).
475-
// useId relies on the component tree structure to generate deterministic id's
476-
// so we need to ensure it remains the same on the client even though
477-
// we don't need the <script> tag
478-
return (
479-
<>
480-
<DataRouterContext.Provider value={dataRouterContext}>
481-
<DataRouterStateContext.Provider
482-
value={{
483-
...state,
484-
revalidation: revalidating ? "loading" : state.revalidation,
485-
}}
486-
>
487-
<FetchersContext.Provider value={fetcherData.current}>
488-
{/* <ViewTransitionContext.Provider value={vtContext}> */}
489-
<Router
490-
basename={basename}
491-
location={state.location}
492-
navigationType={state.historyAction}
493-
navigator={navigator}
494-
unstable_transitions={true}
495-
>
496-
<MemoizedDataRoutes
497-
routes={router.routes}
498-
future={router.future}
499-
state={state}
500-
unstable_onError={unstable_onError}
501-
/>
502-
</Router>
503-
{/* </ViewTransitionContext.Provider> */}
504-
</FetchersContext.Provider>
505-
</DataRouterStateContext.Provider>
506-
</DataRouterContext.Provider>
507-
{null}
508-
</>
509-
);
510-
}
511-
512374
/**
513375
* Render the UI for the given {@link DataRouter}. This component should
514376
* typically be at the top of an app's element tree.
@@ -548,6 +410,8 @@ export function RouterProvider({
548410
unstable_transitions,
549411
}: RouterProviderProps): React.ReactElement {
550412
let [_state, setStateImpl] = React.useState(router.state);
413+
let navigationRef = React.useRef(_state.navigation);
414+
let [pending, startTransition] = React.useTransition();
551415
// @ts-expect-error - Needs React 19 types
552416
let [state, setOptimisticState] = React.useOptimistic(_state);
553417
let [pendingState, setPendingState] = React.useState<RouterState>();
@@ -624,9 +488,16 @@ export function RouterProvider({
624488
} else if (unstable_transitions === false) {
625489
logErrorsAndSetState(newState);
626490
} else {
627-
React.startTransition(() => {
491+
startTransition(() => {
628492
if (unstable_transitions === true) {
629-
setOptimisticState(newState);
493+
if (newState.navigation.state !== "idle") {
494+
navigationRef.current = newState.navigation;
495+
}
496+
setOptimisticState((state: any) => ({
497+
...state,
498+
navigation: navigationRef.current,
499+
revalidation: newState.revalidation,
500+
}));
630501
}
631502
logErrorsAndSetState(newState);
632503
});
@@ -726,9 +597,16 @@ export function RouterProvider({
726597
if (unstable_transitions === false) {
727598
logErrorsAndSetState(newState);
728599
} else {
729-
React.startTransition(() => {
600+
startTransition(() => {
730601
if (unstable_transitions === true) {
731-
setOptimisticState(newState);
602+
if (newState.navigation.state !== "idle") {
603+
navigationRef.current = newState.navigation;
604+
}
605+
setOptimisticState((state: any) => ({
606+
...state,
607+
navigation: newState.navigation,
608+
revalidation: newState.revalidation,
609+
}));
732610
}
733611
logErrorsAndSetState(newState);
734612
});
@@ -783,18 +661,59 @@ export function RouterProvider({
783661
return {
784662
createHref: router.createHref,
785663
encodeLocation: router.encodeLocation,
786-
go: (n) => router.navigate(n),
787-
push: (to, state, opts) =>
788-
router.navigate(to, {
664+
go: (n) => {
665+
if (unstable_transitions === true) {
666+
const resolver = new Deferred<void>();
667+
// @ts-expect-error - Needs React 19 types
668+
startTransition(() => {
669+
return router.navigate(n).then(resolver.resolve, resolver.reject);
670+
});
671+
return resolver.promise;
672+
}
673+
return router.navigate(n);
674+
},
675+
push: (to, state, opts) => {
676+
if (unstable_transitions === true) {
677+
const resolver = new Deferred<void>();
678+
// @ts-expect-error - Needs React 19 types
679+
startTransition(() => {
680+
return router
681+
.navigate(to, {
682+
state,
683+
preventScrollReset: opts?.preventScrollReset,
684+
})
685+
.then(resolver.resolve, resolver.reject);
686+
});
687+
return resolver.promise;
688+
}
689+
690+
return router.navigate(to, {
789691
state,
790692
preventScrollReset: opts?.preventScrollReset,
791-
}),
792-
replace: (to, state, opts) =>
793-
router.navigate(to, {
693+
});
694+
},
695+
replace: (to, state, opts) => {
696+
if (unstable_transitions === true) {
697+
const resolver = new Deferred<void>();
698+
// @ts-expect-error - Needs React 19 types
699+
startTransition(() => {
700+
return router
701+
.navigate(to, {
702+
replace: true,
703+
state,
704+
preventScrollReset: opts?.preventScrollReset,
705+
})
706+
.then(resolver.resolve, resolver.reject);
707+
});
708+
return resolver.promise;
709+
}
710+
711+
return router.navigate(to, {
794712
replace: true,
795713
state,
796714
preventScrollReset: opts?.preventScrollReset,
797-
}),
715+
});
716+
},
798717
};
799718
}, [router]);
800719

@@ -833,7 +752,14 @@ export function RouterProvider({
833752
<MemoizedDataRoutes
834753
routes={router.routes}
835754
future={router.future}
836-
state={state}
755+
state={
756+
pending
757+
? {
758+
...state,
759+
navigation: navigationRef.current,
760+
}
761+
: state
762+
}
837763
unstable_onError={unstable_onError}
838764
/>
839765
</Router>

0 commit comments

Comments
 (0)