import * as React from 'react' import invariant from 'tiny-invariant' import warning from 'tiny-warning' import { createControlledPromise, getLocationChangeInfo, isNotFound, isRedirect, pick, rootRouteId, } from '@tanstack/router-core' import { CatchBoundary, ErrorComponent } from './CatchBoundary' import { useRouterState } from './useRouterState' import { useRouter } from './useRouter' import { CatchNotFound } from './not-found' import { matchContext } from './matchContext' import { SafeFragment } from './SafeFragment' import { renderRouteNotFound } from './renderRouteNotFound' import { ScrollRestoration } from './scroll-restoration' import type { AnyRoute, ParsedLocation } from '@tanstack/router-core' export const Match = React.memo(function MatchImpl({ matchId, }: { matchId: string }) { const router = useRouter() const routeId = useRouterState({ select: (s) => s.matches.find((d) => d.id === matchId)?.routeId as string, }) invariant( routeId, `Could not find routeId for matchId "${matchId}". Please file an issue!`, ) const route: AnyRoute = router.routesById[routeId] const PendingComponent = route.options.pendingComponent ?? router.options.defaultPendingComponent const pendingElement = PendingComponent ? : null const routeErrorComponent = route.options.errorComponent ?? router.options.defaultErrorComponent const routeOnCatch = route.options.onCatch ?? router.options.defaultOnCatch const routeNotFoundComponent = route.isRoot ? // If it's the root route, use the globalNotFound option, with fallback to the notFoundRoute's component (route.options.notFoundComponent ?? router.options.notFoundRoute?.options.component) : route.options.notFoundComponent const ResolvedSuspenseBoundary = // If we're on the root route, allow forcefully wrapping in suspense (!route.isRoot || route.options.wrapInSuspense) && (route.options.wrapInSuspense ?? PendingComponent ?? (route.options.errorComponent as any)?.preload) ? React.Suspense : SafeFragment const ResolvedCatchBoundary = routeErrorComponent ? CatchBoundary : SafeFragment const ResolvedNotFoundBoundary = routeNotFoundComponent ? CatchNotFound : SafeFragment const resetKey = useRouterState({ select: (s) => s.loadedAt, }) const parentRouteId = useRouterState({ select: (s) => { const index = s.matches.findIndex((d) => d.id === matchId) return s.matches[index - 1]?.routeId as string }, }) return ( <> resetKey} errorComponent={routeErrorComponent || ErrorComponent} onCatch={(error, errorInfo) => { // Forward not found errors (we don't want to show the error component for these) if (isNotFound(error)) throw error warning(false, `Error in route match: ${matchId}`) routeOnCatch?.(error, errorInfo) }} > { // If the current not found handler doesn't exist or it has a // route ID which doesn't match the current route, rethrow the error if ( !routeNotFoundComponent || (error.routeId && error.routeId !== routeId) || (!error.routeId && !route.isRoot) ) throw error return React.createElement(routeNotFoundComponent, error as any) }} > {parentRouteId === rootRouteId && router.options.scrollRestoration ? ( <> ) : null} ) }) // On Rendered can't happen above the root layout because it actually // renders a dummy dom element to track the rendered state of the app. // We render a script tag with a key that changes based on the current // location state.key. Also, because it's below the root layout, it // allows us to fire onRendered events even after a hydration mismatch // error that occurred above the root layout (like bad head/link tags, // which is common). function OnRendered() { const router = useRouter() const prevLocationRef = React.useRef>( undefined, ) return (