'use client' import * as React from 'react' import { useStore } from '@tanstack/react-store' import { replaceEqualDeep, rootRouteId } from '@tanstack/router-core' import { isServer } from '@tanstack/router-core/isServer' import { CatchBoundary, ErrorComponent } from './CatchBoundary' import { useRouter } from './useRouter' import { Transitioner } from './Transitioner' import { matchContext } from './matchContext' import { Match } from './Match' import { SafeFragment } from './SafeFragment' import type { StructuralSharingOption, ValidateSelected, } from './structuralSharing' import type { AnyRoute, AnyRouter, DeepPartial, Expand, MakeOptionalPathParams, MakeOptionalSearchParams, MakeRouteMatchUnion, MaskOptions, MatchRouteOptions, RegisteredRouter, ResolveRoute, ToSubOptionsProps, } from '@tanstack/router-core' declare module '@tanstack/router-core' { export interface RouteMatchExtensions { meta?: Array links?: Array scripts?: Array styles?: Array headScripts?: Array } } /** * Internal component that renders the router's active match tree with * suspense, error, and not-found boundaries. Rendered by `RouterProvider`. */ export function Matches() { const router = useRouter() const rootRoute: AnyRoute = router.routesById[rootRouteId] const PendingComponent = rootRoute.options.pendingComponent ?? router.options.defaultPendingComponent const pendingElement = PendingComponent ? : null // Do not render a root Suspense during SSR or hydrating from SSR const ResolvedSuspense = (isServer ?? router.isServer) || (typeof document !== 'undefined' && router.ssr) ? SafeFragment : React.Suspense const inner = ( {!(isServer ?? router.isServer) && } ) return router.options.InnerWrap ? ( {inner} ) : ( inner ) } function MatchesInner() { const router = useRouter() const _isServer = isServer ?? router.isServer const matchId = _isServer ? router.stores.firstId.get() : // eslint-disable-next-line react-hooks/rules-of-hooks useStore(router.stores.firstId, (id) => id) const resetKey = _isServer ? router.stores.loadedAt.get() : // eslint-disable-next-line react-hooks/rules-of-hooks useStore(router.stores.loadedAt, (loadedAt) => loadedAt) const matchComponent = matchId ? : null return ( {router.options.disableGlobalCatchBoundary ? ( matchComponent ) : ( resetKey} errorComponent={ErrorComponent} onCatch={ process.env.NODE_ENV !== 'production' ? (error) => { console.warn( `Warning: The following error wasn't caught by any route! At the very least, consider setting an 'errorComponent' in your RootRoute!`, ) console.warn(`Warning: ${error.message || error.toString()}`) } : undefined } > {matchComponent} )} ) } export type UseMatchRouteOptions< TRouter extends AnyRouter = RegisteredRouter, TFrom extends string = string, TTo extends string | undefined = undefined, TMaskFrom extends string = TFrom, TMaskTo extends string = '', > = ToSubOptionsProps & DeepPartial> & DeepPartial> & MaskOptions & MatchRouteOptions /** * Create a matcher function for testing locations against route definitions. * * The returned function accepts standard navigation options (`to`, `params`, * `search`, etc.) and returns either `false` (no match) or the matched params * object when the route matches the current or pending location. * * Useful for conditional rendering and active UI states. * * @returns A `matchRoute(options)` function that returns `false` or params. * @link https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchRouteHook */ export function useMatchRoute() { const router = useRouter() if (!(isServer ?? router.isServer)) { // eslint-disable-next-line react-hooks/rules-of-hooks useStore(router.stores.matchRouteDeps, (d) => d) } return React.useCallback( < const TFrom extends string = string, const TTo extends string | undefined = undefined, const TMaskFrom extends string = TFrom, const TMaskTo extends string = '', >( opts: UseMatchRouteOptions, ): | false | Expand['types']['allParams']> => { const { pending, caseSensitive, fuzzy, includeSearch, ...rest } = opts return router.matchRoute(rest as any, { pending, caseSensitive, fuzzy, includeSearch, }) }, [router], ) } export type MakeMatchRouteOptions< TRouter extends AnyRouter = RegisteredRouter, TFrom extends string = string, TTo extends string | undefined = undefined, TMaskFrom extends string = TFrom, TMaskTo extends string = '', > = UseMatchRouteOptions & { // If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns children?: | (( params?: Expand< ResolveRoute['types']['allParams'] >, ) => React.ReactNode) | React.ReactNode } /** * Component that conditionally renders its children based on whether a route * matches the provided `from`/`to` options. If `children` is a function, it * receives the matched params object. * * @link https://tanstack.com/router/latest/docs/framework/react/api/router/matchRouteComponent */ export function MatchRoute< TRouter extends AnyRouter = RegisteredRouter, const TFrom extends string = string, const TTo extends string | undefined = undefined, const TMaskFrom extends string = TFrom, const TMaskTo extends string = '', >(props: MakeMatchRouteOptions): any { const matchRoute = useMatchRoute() const params = matchRoute(props as any) as boolean if (typeof props.children === 'function') { return (props.children as any)(params) } return params ? props.children : null } export interface UseMatchesBaseOptions< TRouter extends AnyRouter, TSelected, TStructuralSharing, > { select?: ( matches: Array>, ) => ValidateSelected } export type UseMatchesResult< TRouter extends AnyRouter, TSelected, > = unknown extends TSelected ? Array> : TSelected export function useMatches< TRouter extends AnyRouter = RegisteredRouter, TSelected = unknown, TStructuralSharing extends boolean = boolean, >( opts?: UseMatchesBaseOptions & StructuralSharingOption, ): UseMatchesResult { const router = useRouter() const previousResult = React.useRef>( undefined, ) if (isServer ?? router.isServer) { const matches = router.stores.matches.get() as Array< MakeRouteMatchUnion > return (opts?.select ? opts.select(matches) : matches) as UseMatchesResult< TRouter, TSelected > } // eslint-disable-next-line react-hooks/rules-of-hooks return useStore(router.stores.matches, (matches) => { const selected = opts?.select ? opts.select(matches as Array>) : (matches as any) if (opts?.structuralSharing ?? router.options.defaultStructuralSharing) { const shared = replaceEqualDeep(previousResult.current, selected) previousResult.current = shared return shared } return selected }) as UseMatchesResult } /** * Read the full array of active route matches or select a derived subset. * * Useful for debugging, breadcrumbs, or aggregating metadata across matches. * * @returns The array of matches (or the selected value). * @link https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchesHook */ /** * Read the full array of active route matches or select a derived subset. * * Useful for debugging, breadcrumbs, or aggregating metadata across matches. * * @link https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchesHook */ export function useParentMatches< TRouter extends AnyRouter = RegisteredRouter, TSelected = unknown, TStructuralSharing extends boolean = boolean, >( opts?: UseMatchesBaseOptions & StructuralSharingOption, ): UseMatchesResult { const contextMatchId = React.useContext(matchContext) return useMatches({ select: (matches: Array>) => { matches = matches.slice( 0, matches.findIndex((d) => d.id === contextMatchId), ) return opts?.select ? opts.select(matches) : matches }, structuralSharing: opts?.structuralSharing, } as any) } /** * Read the array of active route matches that are children of the current * match (or selected parent) in the match tree. */ export function useChildMatches< TRouter extends AnyRouter = RegisteredRouter, TSelected = unknown, TStructuralSharing extends boolean = boolean, >( opts?: UseMatchesBaseOptions & StructuralSharingOption, ): UseMatchesResult { const contextMatchId = React.useContext(matchContext) return useMatches({ select: (matches: Array>) => { matches = matches.slice( matches.findIndex((d) => d.id === contextMatchId) + 1, ) return opts?.select ? opts.select(matches) : matches }, structuralSharing: opts?.structuralSharing, } as any) }