@tanstack/solid-router
Version:
Modern and scalable routing for Solid applications
203 lines (202 loc) • 9.87 kB
JSX
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