@tanstack/solid-router
Version:
Modern and scalable routing for Solid applications
146 lines • 6.16 kB
JSX
import * as Solid from 'solid-js';
import { getLocationChangeInfo, trimPathRight } from '@tanstack/router-core';
import { useRouter } from './useRouter';
/**
* Inline version of handleHashScroll that accepts a pre-captured location
* to avoid reading router.stores.location.state inside an effect callback
* (which would trigger a Solid v2 reactive warning).
*/
function handleHashScrollWithLocation(_router, location) {
if (typeof document !== 'undefined' && document.querySelector) {
const hashScrollIntoViewOptions = location.state.__hashScrollIntoViewOptions ?? true;
if (hashScrollIntoViewOptions && location.hash !== '') {
const el = document.getElementById(location.hash);
if (el) {
el.scrollIntoView(hashScrollIntoViewOptions);
}
}
}
}
export function Transitioner() {
const router = useRouter();
let mountLoadForRouter = { router, mounted: false };
const isLoading = Solid.createMemo(() => router.stores.isLoading.state);
const [isSolidTransitioning] = [() => false];
// Track pending state changes
const hasPendingMatches = Solid.createMemo(() => router.stores.hasPendingMatches.state);
const isAnyPending = Solid.createMemo(() => isLoading() || isSolidTransitioning() || hasPendingMatches());
const isPagePending = Solid.createMemo(() => isLoading() || hasPendingMatches());
router.startTransition = (fn) => {
Solid.runWithOwner(null, fn);
try {
Solid.flush();
}
catch {
// flush() throws inside reactive contexts — Solid auto-flushes there
}
};
// Subscribe to location changes
// and try to load the new location
Solid.onSettled(() => {
const unsub = router.history.subscribe(() => {
queueMicrotask(() => router.load());
});
// Refresh latestLocation from the current browser URL before comparing.
// The URL may have been changed synchronously (e.g. via replaceState) after
// render() but before this effect ran, so we must not use the stale
// render-time location here.
router.updateLatestLocation();
const nextLocation = router.buildLocation({
to: router.latestLocation.pathname,
search: true,
params: true,
hash: true,
state: true,
_includeValidateSearch: true,
});
// Check if the current URL matches the canonical form.
// Compare publicHref (browser-facing URL) for consistency with
// the server-side redirect check in router.beforeLoad.
if (trimPathRight(router.latestLocation.publicHref) !==
trimPathRight(nextLocation.publicHref)) {
router.commitLocation({ ...nextLocation, replace: true });
}
return () => {
unsub();
};
});
// Try to load the initial location
// In Solid v2, signal updates inside onSettled cannot be flushed
// synchronously (flush() throws). router.load() sets signals via batch(),
// and the code that runs immediately after needs those values committed.
// By deferring to queueMicrotask, the load runs outside the reactive
// scheduling frame so flush() works correctly.
Solid.onSettled(() => {
if (
// if we are hydrating from SSR, loading is triggered in ssr-client
(typeof window !== 'undefined' && router.ssr) ||
(mountLoadForRouter.router === router && mountLoadForRouter.mounted)) {
return;
}
mountLoadForRouter = { router, mounted: true };
queueMicrotask(() => {
const tryLoad = async () => {
try {
await router.load();
}
catch (err) {
console.error(err);
}
};
tryLoad();
});
});
Solid.createRenderEffect(() => [
isLoading(),
isPagePending(),
isAnyPending(),
router.stores.location.state,
router.stores.resolvedLocation.state,
], ([currentIsLoading, currentIsPagePending, currentIsAnyPending, loc, resolvedLoc,], prev) => {
// Guard: if location state isn't available yet, skip all event emissions
if (!loc)
return;
const previousIsLoading = prev?.[0];
const previousIsPagePending = prev?.[1];
const previousIsAnyPending = prev?.[2];
// onLoad: when the router finishes loading
if (previousIsLoading && !currentIsLoading) {
router.emit({
type: 'onLoad',
...getLocationChangeInfo(loc, resolvedLoc),
});
}
// onBeforeRouteMount: must fire before onResolved
if (previousIsPagePending && !currentIsPagePending) {
router.emit({
type: 'onBeforeRouteMount',
...getLocationChangeInfo(loc, resolvedLoc),
});
}
// onResolved: fires after onBeforeRouteMount
if (previousIsAnyPending && !currentIsAnyPending) {
const changeInfo = getLocationChangeInfo(loc, resolvedLoc);
router.emit({
type: 'onResolved',
...changeInfo,
});
Solid.runWithOwner(null, () => {
router.batch(() => {
router.stores.status.setState(() => 'idle');
// Use `loc` from the source tuple to avoid reading
// router.stores.location.state inside the effect callback
router.stores.resolvedLocation.setState(() => loc);
});
});
if (changeInfo.hrefChanged) {
// Pass the already-captured location to avoid a reactive read
// inside the effect callback (handleHashScroll would otherwise
// read router.stores.location.state which triggers a warning)
handleHashScrollWithLocation(router, loc);
}
}
});
return null;
}
//# sourceMappingURL=Transitioner.jsx.map