UNPKG

@tanstack/solid-router

Version:

Modern and scalable routing for Solid applications

146 lines 6.16 kB
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