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 (