UNPKG

@tanstack/solid-router

Version:

Modern and scalable routing for Solid applications

203 lines (202 loc) 9.87 kB
import * as Solid from 'solid-js'; import { rootRouteId } from '@tanstack/router-core'; import { isServer } from '@tanstack/router-core/isServer'; import { Dynamic } from 'solid-js/web'; import { CatchBoundary, ErrorComponent } from './CatchBoundary'; import { useRouter } from './useRouter'; import { CatchNotFound, getNotFound } from './not-found'; import { nearestMatchContext } from './matchContext'; import { SafeFragment } from './SafeFragment'; import { renderRouteNotFound } from './renderRouteNotFound'; import { ScrollRestoration } from './scroll-restoration'; import { ClientOnly } from './ClientOnly'; import { nonRouteComponentContext, renderInNonRouteComponentContext, } from './nonRouteComponentContext'; // Keep the client constant undefined so the server-only script can be dropped. const renderScrollRestoration = isServer === false ? undefined : (router, route) => (isServer ?? router.isServer) && route.parentRoute?.id === rootRouteId && router.options.scrollRestoration ? (<ScrollRestoration />) : null; export const Match = (props) => { const router = useRouter(); const currentMatch = Solid.createMemo(() => router.stores.byRoute.get(props.routeId).get()); const nearestMatch = [() => props.routeId, currentMatch]; const route = router.routesById[props.routeId]; // Lazy route option mutations become observable with the next client match // publication. Server stores are non-reactive and options load before render. const routeOptions = (isServer ?? router.isServer) ? () => route.options : () => { currentMatch(); return route.options; }; const resolvePendingComponent = () => routeOptions().pendingComponent ?? router.options.defaultPendingComponent; const routeErrorComponent = () => routeOptions().errorComponent ?? router.options.defaultErrorComponent; const routeNotFoundComponent = () => route.isRoot ? // If it's the root route, use the _notFound option, with fallback to the notFoundRoute's component (routeOptions().notFoundComponent ?? router.options.notFoundRoute?.options.component) : routeOptions().notFoundComponent; const resolvedNoSsr = () => currentMatch().ssr === false || currentMatch().ssr === 'data-only'; const shouldSkipSuspenseFallback = () => (isServer ?? router.isServer) ? resolvedNoSsr() : currentMatch().ssr === 'data-only'; const ShellComponent = route.isRoot ? (route.options.shellComponent ?? SafeFragment) : SafeFragment; const MatchContent = () => (<Solid.Show when={currentMatch().status !== 'pending'} fallback={(() => { if (process.env.NODE_ENV !== 'production') { return renderInNonRouteComponentContext(() => <Dynamic component={resolvePendingComponent()}/>, 'pendingComponent'); } return <Dynamic component={resolvePendingComponent()}/>; })()}> <MatchInner /> </Solid.Show>); return (<ShellComponent> <nearestMatchContext.Provider value={nearestMatch}> <Solid.Suspense fallback={(() => { // Data-only SSR renders the inner fallback on the server, so // avoid adding an extra suspense fallback on the client. if (shouldSkipSuspenseFallback()) { return undefined; } if (process.env.NODE_ENV !== 'production') { return renderInNonRouteComponentContext(() => <Dynamic component={resolvePendingComponent()}/>, 'pendingComponent'); } return <Dynamic component={resolvePendingComponent()}/>; })()}> <Dynamic component={routeErrorComponent() ? CatchBoundary : SafeFragment} getResetKey={currentMatch} errorComponent={routeErrorComponent()} onCatch={(error) => { // Forward not found errors (we don't want to show the error component for these) const notFoundError = getNotFound(error); if (notFoundError) { notFoundError.routeId ?? (notFoundError.routeId = currentMatch().routeId); throw notFoundError; } if (process.env.NODE_ENV !== 'production') { console.warn(`Warning: Error in route match: ${currentMatch().routeId}`); } ; (route.options.onCatch ?? router.options.defaultOnCatch)?.(error); }}> <Dynamic component={routeNotFoundComponent() ? CatchNotFound : SafeFragment} fallback={(error) => { const notFoundError = getNotFound(error) ?? error; notFoundError.routeId ?? (notFoundError.routeId = currentMatch().routeId); if (notFoundError.routeId !== currentMatch().routeId) { throw notFoundError; } return process.env.NODE_ENV !== 'production' ? (renderInNonRouteComponentContext(() => (<Dynamic component={routeNotFoundComponent()} {...notFoundError}/>), 'notFoundComponent')) : (<Dynamic component={routeNotFoundComponent()} {...notFoundError}/>); }}> <Solid.Switch> <Solid.Match when={resolvedNoSsr()}> <ClientOnly fallback={(() => { if (process.env.NODE_ENV !== 'production') { return renderInNonRouteComponentContext(() => (<Dynamic component={resolvePendingComponent()}/>), 'pendingComponent'); } return <Dynamic component={resolvePendingComponent()}/>; })()}> <MatchContent /> </ClientOnly> </Solid.Match> <Solid.Match when={!resolvedNoSsr()}> <MatchContent /> </Solid.Match> </Solid.Switch> </Dynamic> </Dynamic> </Solid.Suspense> </nearestMatchContext.Provider> {renderScrollRestoration?.(router, route)} </ShellComponent>); }; export const MatchInner = () => { const router = useRouter(); const nearestMatch = Solid.useContext(nearestMatchContext); const match = nearestMatch[1 /* match */]; const routeId = nearestMatch[0 /* route id */]; const route = router.routesById[routeId()]; const currentMatch = () => match(); const componentKey = () => { const current = currentMatch(); const remount = route.options.remountDeps ?? router.options.defaultRemountDeps; if (!remount) { return routeId(); } const deps = remount({ routeId: routeId(), loaderDeps: current.loaderDeps, params: current._strictParams, search: current._strictSearch, }); return JSON.stringify(deps) ?? routeId(); }; const out = () => { const Comp = route.options.component ?? router.options.defaultComponent; if (Comp) { return <Comp />; } return <Outlet />; }; const keyedOut = () => (<Solid.Show when={componentKey()} keyed> {(_key) => out()} </Solid.Show>); return (<Solid.Switch> <Solid.Match when={currentMatch().status === 'notFound'}> {(_) => renderRouteNotFound(router, route, currentMatch().error)} </Solid.Match> <Solid.Match when={currentMatch().status === 'error'}> {(_) => { if (isServer ?? router.isServer) { const RouteErrorComponent = (route.options.errorComponent ?? router.options.defaultErrorComponent) || ErrorComponent; return process.env.NODE_ENV !== 'production' ? (renderInNonRouteComponentContext(() => (<RouteErrorComponent error={currentMatch().error} info={{ componentStack: '', }}/>), 'errorComponent')) : (<RouteErrorComponent error={currentMatch().error} info={{ componentStack: '', }}/>); } throw currentMatch().error; }} </Solid.Match> <Solid.Match when={currentMatch().status === 'success'}> {keyedOut()} </Solid.Match> </Solid.Switch>); }; export const Outlet = () => { if (process.env.NODE_ENV !== 'production') { const nonRouteComponent = Solid.useContext(nonRouteComponentContext); if (nonRouteComponent) { console.warn(`Warning: An <Outlet /> was rendered inside a ${nonRouteComponent}. <Outlet /> should only be rendered inside a route component.`); } } const router = useRouter(); const nearestParentMatch = Solid.useContext(nearestMatchContext); const parentMatch = nearestParentMatch[1 /* match */]; const routeId = nearestParentMatch[0 /* route id */]; const route = router.routesById[routeId()]; const childRouteId = () => { if (parentMatch()._notFound) { return; } const ids = router.stores.ids.get(); return ids[ids.indexOf(routeId()) + 1]; }; return (<Solid.Show when={childRouteId()} keyed fallback={parentMatch()._notFound ? renderRouteNotFound(router, route, parentMatch().error) : undefined}> {(_routeKey) => { return (<Solid.Show when={routeId() === rootRouteId} fallback={<Match routeId={childRouteId()}/>}> <Solid.Suspense fallback={(() => { if (process.env.NODE_ENV !== 'production') { return renderInNonRouteComponentContext(() => (<Dynamic component={router.options.defaultPendingComponent}/>), 'pendingComponent'); } return (<Dynamic component={router.options.defaultPendingComponent}/>); })()}> <Match routeId={childRouteId()}/> </Solid.Suspense> </Solid.Show>); }} </Solid.Show>); }; //# sourceMappingURL=Match.jsx.map