Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 68 additions & 147 deletions packages/react-router/lib/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -371,144 +371,6 @@ export interface RouterProviderProps {
unstable_transitions?: boolean;
}

function shallowDiff(a: any, b: any) {
if (a === b) {
return false;
}
let aKeys = Object.keys(a);
let bKeys = Object.keys(b);
if (aKeys.length !== bKeys.length) {
return true;
}
for (let key of aKeys) {
if (a[key] !== b[key]) {
return true;
}
}
return false;
}

export function UNSTABLE_TransitionEnabledRouterProvider({
router,
flushSync: reactDomFlushSyncImpl,
unstable_onError,
}: Omit<RouterProviderProps, "unstable_transitions">): React.ReactElement {
let fetcherData = React.useRef<Map<string, any>>(new Map());
let [revalidating, startRevalidation] = React.useTransition();
let [state, setState] = React.useState(router.state);

(router as any).__setPendingRerender = (promise: Promise<() => void>) =>
startRevalidation(
// @ts-expect-error - need react 19 types for this to be async
async () => {
const rerender = await promise;
startRevalidation(() => {
rerender();
});
},
);

let navigator = React.useMemo((): Navigator => {
return {
createHref: router.createHref,
encodeLocation: router.encodeLocation,
go: (n) => router.navigate(n),
push: (to, state, opts) =>
router.navigate(to, {
state,
preventScrollReset: opts?.preventScrollReset,
}),
replace: (to, state, opts) =>
router.navigate(to, {
replace: true,
state,
preventScrollReset: opts?.preventScrollReset,
}),
};
}, [router]);

let basename = router.basename || "/";

let dataRouterContext = React.useMemo(
() => ({
router,
navigator,
static: false,
basename,
unstable_onError,
}),
[router, navigator, basename, unstable_onError],
);

React.useLayoutEffect(() => {
return router.subscribe(
(newState, { deletedFetchers, flushSync, viewTransitionOpts }) => {
newState.fetchers.forEach((fetcher, key) => {
if (fetcher.data !== undefined) {
fetcherData.current.set(key, fetcher.data);
}
});
deletedFetchers.forEach((key) => fetcherData.current.delete(key));

const diff = shallowDiff(state, newState);

if (!diff) return;

if (flushSync) {
if (reactDomFlushSyncImpl) {
reactDomFlushSyncImpl(() => setState(newState));
} else {
setState(newState);
}
} else {
React.startTransition(() => {
setState(newState);
});
}
},
);
}, [router, reactDomFlushSyncImpl, state]);

// The fragment and {null} here are important! We need them to keep React 18's
// useId happy when we are server-rendering since we may have a <script> here
// containing the hydrated server-side staticContext (from StaticRouterProvider).
// useId relies on the component tree structure to generate deterministic id's
// so we need to ensure it remains the same on the client even though
// we don't need the <script> tag
return (
<>
<DataRouterContext.Provider value={dataRouterContext}>
<DataRouterStateContext.Provider
value={{
...state,
revalidation: revalidating ? "loading" : state.revalidation,
}}
>
<FetchersContext.Provider value={fetcherData.current}>
{/* <ViewTransitionContext.Provider value={vtContext}> */}
<Router
basename={basename}
location={state.location}
navigationType={state.historyAction}
navigator={navigator}
unstable_transitions={true}
>
<MemoizedDataRoutes
routes={router.routes}
future={router.future}
state={state}
unstable_onError={unstable_onError}
/>
</Router>
{/* </ViewTransitionContext.Provider> */}
</FetchersContext.Provider>
</DataRouterStateContext.Provider>
</DataRouterContext.Provider>
{null}
</>
);
}

/**
* Render the UI for the given {@link DataRouter}. This component should
* typically be at the top of an app's element tree.
Expand Down Expand Up @@ -626,7 +488,18 @@ export function RouterProvider({
} else {
React.startTransition(() => {
if (unstable_transitions === true) {
setOptimisticState(newState);
// @ts-expect-error - Needs React 19 types
setOptimisticState((state) => ({
...state,
navigation:
newState.navigation.state !== "idle"
? newState.navigation
: state.navigation,
revalidation:
newState.revalidation !== "idle"
? newState.revalidation
: state.revalidation,
}));
}
logErrorsAndSetState(newState);
});
Expand Down Expand Up @@ -728,7 +601,11 @@ export function RouterProvider({
} else {
React.startTransition(() => {
if (unstable_transitions === true) {
setOptimisticState(newState);
setOptimisticState((state: any) => ({
...state,
navigation: newState.navigation,
revalidation: newState.revalidation,
}));
}
logErrorsAndSetState(newState);
});
Expand Down Expand Up @@ -783,18 +660,62 @@ export function RouterProvider({
return {
createHref: router.createHref,
encodeLocation: router.encodeLocation,
go: (n) => router.navigate(n),
push: (to, state, opts) =>
router.navigate(to, {
go: (n) => {
if (unstable_transitions === true) {
const resolver = new Deferred<void>();
// @ts-expect-error - Needs React 19 types
startTransition(() => {
return router
.navigate(n)
.then(resolver.resolve)
.catch(resolver.reject);
});
return resolver.promise;
}
return router.navigate(n);
},
push: (to, state, opts) => {
if (unstable_transitions === true) {
const resolver = new Deferred<void>();
// @ts-expect-error - Needs React 19 types
startTransition(() => {
return router
.navigate(to, {
state,
preventScrollReset: opts?.preventScrollReset,
})
.then(resolver.resolve, resolver.reject);
});
return resolver.promise;
}

return router.navigate(to, {
state,
preventScrollReset: opts?.preventScrollReset,
}),
replace: (to, state, opts) =>
router.navigate(to, {
});
},
replace: (to, state, opts) => {
if (unstable_transitions === true) {
const resolver = new Deferred<void>();
// @ts-expect-error - Needs React 19 types
startTransition(() => {
return router
.navigate(to, {
replace: true,
state,
preventScrollReset: opts?.preventScrollReset,
})
.then(resolver.resolve, resolver.reject);
});
return resolver.promise;
}

return router.navigate(to, {
replace: true,
state,
preventScrollReset: opts?.preventScrollReset,
}),
});
},
};
}, [router]);

Expand Down
2 changes: 1 addition & 1 deletion packages/react-router/lib/dom/lib.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2938,7 +2938,7 @@ export function useFetcher<T = any>({
React.useEffect(() => {
router.getFetcher(fetcherKey);
return () => router.deleteFetcher(fetcherKey);
}, [router, fetcherKey]);
}, [router.getFetcher, router.deleteFetcher, fetcherKey]);

// Fetcher additions
let load = React.useCallback(
Expand Down
Loading
Loading