Skip to content

Commit 30b056d

Browse files
authored
fix: enable RSC full transition support
1 parent 677dc94 commit 30b056d

File tree

3 files changed

+114
-184
lines changed

3 files changed

+114
-184
lines changed

packages/react-router/lib/components.tsx

Lines changed: 0 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -392,144 +392,6 @@ export interface RouterProviderProps {
392392
unstable_useTransitions?: boolean;
393393
}
394394

395-
function shallowDiff(a: any, b: any) {
396-
if (a === b) {
397-
return false;
398-
}
399-
let aKeys = Object.keys(a);
400-
let bKeys = Object.keys(b);
401-
if (aKeys.length !== bKeys.length) {
402-
return true;
403-
}
404-
for (let key of aKeys) {
405-
if (a[key] !== b[key]) {
406-
return true;
407-
}
408-
}
409-
return false;
410-
}
411-
412-
export function UNSTABLE_TransitionEnabledRouterProvider({
413-
router,
414-
flushSync: reactDomFlushSyncImpl,
415-
unstable_onError,
416-
}: Omit<RouterProviderProps, "unstable_useTransitions">): React.ReactElement {
417-
let fetcherData = React.useRef<Map<string, any>>(new Map());
418-
let [revalidating, startRevalidation] = React.useTransition();
419-
let [state, setState] = React.useState(router.state);
420-
421-
(router as any).__setPendingRerender = (promise: Promise<() => void>) =>
422-
startRevalidation(
423-
// @ts-expect-error - need react 19 types for this to be async
424-
async () => {
425-
const rerender = await promise;
426-
startRevalidation(() => {
427-
rerender();
428-
});
429-
},
430-
);
431-
432-
let navigator = React.useMemo((): Navigator => {
433-
return {
434-
createHref: router.createHref,
435-
encodeLocation: router.encodeLocation,
436-
go: (n) => router.navigate(n),
437-
push: (to, state, opts) =>
438-
router.navigate(to, {
439-
state,
440-
preventScrollReset: opts?.preventScrollReset,
441-
}),
442-
replace: (to, state, opts) =>
443-
router.navigate(to, {
444-
replace: true,
445-
state,
446-
preventScrollReset: opts?.preventScrollReset,
447-
}),
448-
};
449-
}, [router]);
450-
451-
let basename = router.basename || "/";
452-
453-
let dataRouterContext = React.useMemo(
454-
() => ({
455-
router,
456-
navigator,
457-
static: false,
458-
basename,
459-
unstable_onError,
460-
}),
461-
[router, navigator, basename, unstable_onError],
462-
);
463-
464-
React.useLayoutEffect(() => {
465-
return router.subscribe(
466-
(newState, { deletedFetchers, flushSync, viewTransitionOpts }) => {
467-
newState.fetchers.forEach((fetcher, key) => {
468-
if (fetcher.data !== undefined) {
469-
fetcherData.current.set(key, fetcher.data);
470-
}
471-
});
472-
deletedFetchers.forEach((key) => fetcherData.current.delete(key));
473-
474-
const diff = shallowDiff(state, newState);
475-
476-
if (!diff) return;
477-
478-
if (flushSync) {
479-
if (reactDomFlushSyncImpl) {
480-
reactDomFlushSyncImpl(() => setState(newState));
481-
} else {
482-
setState(newState);
483-
}
484-
} else {
485-
React.startTransition(() => {
486-
setState(newState);
487-
});
488-
}
489-
},
490-
);
491-
}, [router, reactDomFlushSyncImpl, state]);
492-
493-
// The fragment and {null} here are important! We need them to keep React 18's
494-
// useId happy when we are server-rendering since we may have a <script> here
495-
// containing the hydrated server-side staticContext (from StaticRouterProvider).
496-
// useId relies on the component tree structure to generate deterministic id's
497-
// so we need to ensure it remains the same on the client even though
498-
// we don't need the <script> tag
499-
return (
500-
<>
501-
<DataRouterContext.Provider value={dataRouterContext}>
502-
<DataRouterStateContext.Provider
503-
value={{
504-
...state,
505-
revalidation: revalidating ? "loading" : state.revalidation,
506-
}}
507-
>
508-
<FetchersContext.Provider value={fetcherData.current}>
509-
{/* <ViewTransitionContext.Provider value={vtContext}> */}
510-
<Router
511-
basename={basename}
512-
location={state.location}
513-
navigationType={state.historyAction}
514-
navigator={navigator}
515-
unstable_useTransitions={true}
516-
>
517-
<MemoizedDataRoutes
518-
routes={router.routes}
519-
future={router.future}
520-
state={state}
521-
unstable_onError={unstable_onError}
522-
/>
523-
</Router>
524-
{/* </ViewTransitionContext.Provider> */}
525-
</FetchersContext.Provider>
526-
</DataRouterStateContext.Provider>
527-
</DataRouterContext.Provider>
528-
{null}
529-
</>
530-
);
531-
}
532-
533395
/**
534396
* Render the UI for the given {@link DataRouter}. This component should
535397
* typically be at the top of an app's element tree.

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

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2603,7 +2603,9 @@ let getUniqueFetcherId = () => `__${String(++fetcherId)}__`;
26032603
* @returns A function that can be called to submit a {@link Form} imperatively.
26042604
*/
26052605
export function useSubmit(): SubmitFunction {
2606-
let { router } = useDataRouterContext(DataRouterHook.UseSubmit);
2606+
let {
2607+
router: { fetch: routerFetch, navigate },
2608+
} = useDataRouterContext(DataRouterHook.UseSubmit);
26072609
let { basename } = React.useContext(NavigationContext);
26082610
let currentRouteId = useRouteId();
26092611

@@ -2616,7 +2618,7 @@ export function useSubmit(): SubmitFunction {
26162618

26172619
if (options.navigate === false) {
26182620
let key = options.fetcherKey || getUniqueFetcherId();
2619-
await router.fetch(key, currentRouteId, options.action || action, {
2621+
await routerFetch(key, currentRouteId, options.action || action, {
26202622
preventScrollReset: options.preventScrollReset,
26212623
formData,
26222624
body,
@@ -2625,7 +2627,7 @@ export function useSubmit(): SubmitFunction {
26252627
flushSync: options.flushSync,
26262628
});
26272629
} else {
2628-
await router.navigate(options.action || action, {
2630+
await navigate(options.action || action, {
26292631
preventScrollReset: options.preventScrollReset,
26302632
formData,
26312633
body,
@@ -2639,7 +2641,7 @@ export function useSubmit(): SubmitFunction {
26392641
});
26402642
}
26412643
},
2642-
[router, basename, currentRouteId],
2644+
[routerFetch, navigate, basename, currentRouteId],
26432645
);
26442646
}
26452647

@@ -2916,7 +2918,9 @@ export function useFetcher<T = any>({
29162918
}: {
29172919
key?: string;
29182920
} = {}): FetcherWithComponents<SerializeFrom<T>> {
2919-
let { router } = useDataRouterContext(DataRouterHook.UseFetcher);
2921+
let {
2922+
router: { deleteFetcher, getFetcher, resetFetcher, fetch: routerFetch },
2923+
} = useDataRouterContext(DataRouterHook.UseFetcher);
29202924
let state = useDataRouterState(DataRouterStateHook.UseFetcher);
29212925
let fetcherData = React.useContext(FetchersContext);
29222926
let route = React.useContext(RouteContext);
@@ -2938,17 +2942,17 @@ export function useFetcher<T = any>({
29382942

29392943
// Registration/cleanup
29402944
React.useEffect(() => {
2941-
router.getFetcher(fetcherKey);
2942-
return () => router.deleteFetcher(fetcherKey);
2943-
}, [router, fetcherKey]);
2945+
getFetcher(fetcherKey);
2946+
return () => deleteFetcher(fetcherKey);
2947+
}, [deleteFetcher, getFetcher, fetcherKey]);
29442948

29452949
// Fetcher additions
29462950
let load = React.useCallback(
29472951
async (href: string, opts?: { flushSync?: boolean }) => {
29482952
invariant(routeId, "No routeId available for fetcher.load()");
2949-
await router.fetch(fetcherKey, routeId, href, opts);
2953+
await routerFetch(fetcherKey, routeId, href, opts);
29502954
},
2951-
[fetcherKey, routeId, router],
2955+
[fetcherKey, routeId, routerFetch],
29522956
);
29532957

29542958
let submitImpl = useSubmit();
@@ -2965,7 +2969,7 @@ export function useFetcher<T = any>({
29652969

29662970
let unstable_reset = React.useCallback<
29672971
FetcherWithComponents<T>["unstable_reset"]
2968-
>((opts) => router.resetFetcher(fetcherKey, opts), [router, fetcherKey]);
2972+
>((opts) => resetFetcher(fetcherKey, opts), [resetFetcher, fetcherKey]);
29692973

29702974
let FetcherForm = React.useMemo(() => {
29712975
let FetcherForm = React.forwardRef<HTMLFormElement, FetcherFormProps>(

0 commit comments

Comments
 (0)