Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 52 additions & 15 deletions packages/react-router/src/Match.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,25 @@ import { renderRouteNotFound } from './renderRouteNotFound'
import { ScrollRestoration } from './scroll-restoration'
import { ClientOnly } from './ClientOnly'
import { useLayoutEffect } from './utils'
import type { AnyRoute, RootRouteOptions } from '@tanstack/router-core'
import type {
AnyRoute,
AnyRouteMatch,
ParsedLocation,
RootRouteOptions,
} from '@tanstack/router-core'

type OutletMatchSelection = [
routeId: string | undefined,
parentGlobalNotFound: boolean,
]

const matchViewFieldsEqual = (a: AnyRouteMatch, b: AnyRouteMatch) =>
a.routeId === b.routeId && a._displayPending === b._displayPending
Comment thread
Sheraff marked this conversation as resolved.

const outletMatchSelectionEqual = (
a: OutletMatchSelection,
b: OutletMatchSelection,
) => a[0] === b[0] && a[1] === b[1]

export const Match = React.memo(function MatchImpl({
matchId,
Expand Down Expand Up @@ -77,7 +95,7 @@ export const Match = React.memo(function MatchImpl({
// eslint-disable-next-line react-hooks/rules-of-hooks
const resetKey = useStore(router.stores.loadedAt, (loadedAt) => loadedAt)
// eslint-disable-next-line react-hooks/rules-of-hooks
const match = useStore(matchStore, (value) => value)
const match = useStore(matchStore, (value) => value, matchViewFieldsEqual)
// eslint-disable-next-line react-hooks/rules-of-hooks
const matchState = React.useMemo(() => {
const routeId = match.routeId as string
Expand Down Expand Up @@ -208,7 +226,7 @@ function MatchView({
</matchContext.Provider>
{matchState.parentRouteId === rootRouteId ? (
<>
<OnRendered resetKey={resetKey} />
<OnRendered />
{router.options.scrollRestoration && (isServer ?? router.isServer) ? (
<ScrollRestoration />
) : null}
Expand All @@ -222,34 +240,49 @@ function MatchView({
// the route subtree has committed below the root layout. Keeping it here lets
// us fire onRendered even after a hydration mismatch above the root layout
// (like bad head/link tags, which is common).
function OnRendered({ resetKey }: { resetKey: number }) {
function OnRendered() {
const router = useRouter()

if (isServer ?? router.isServer) {
return null
}

// Track the resolvedLocation as of the last render so that onRendered can
// report the correct fromLocation. By the time this effect fires,
// resolvedLocation has already been updated to the new location by
// Transitioner, so we cannot use router.stores.resolvedLocation.get()
// directly as the fromLocation.
// @ts-expect-error -- init to `undefined` but don't write `undefined` to shave bytes
// eslint-disable-next-line react-hooks/rules-of-hooks
const prevResolvedLocationRef = React.useRef<
ParsedLocation<any> | undefined
>()
// eslint-disable-next-line react-hooks/rules-of-hooks
const prevHrefRef = React.useRef<string | undefined>(undefined)
const renderedLocationKey = useStore(
router.stores.resolvedLocation,
(resolvedLocation) => resolvedLocation?.state.__TSR_key,
)

// eslint-disable-next-line react-hooks/rules-of-hooks
useLayoutEffect(() => {
const currentHref = router.latestLocation.href
const currentResolvedLocation = router.stores.resolvedLocation.get()
const previousResolvedLocation = prevResolvedLocationRef.current

if (
prevHrefRef.current === undefined ||
prevHrefRef.current !== currentHref
currentResolvedLocation &&
(!previousResolvedLocation ||
previousResolvedLocation.href !== currentResolvedLocation.href)
) {
router.emit({
type: 'onRendered',
...getLocationChangeInfo(
router.stores.location.get(),
router.stores.resolvedLocation.get(),
previousResolvedLocation ?? currentResolvedLocation,
),
})
prevHrefRef.current = currentHref
}
}, [router.latestLocation.state.__TSR_key, resetKey, router])
prevResolvedLocationRef.current = currentResolvedLocation
}, [renderedLocationKey, router])

return null
}
Expand Down Expand Up @@ -521,10 +554,14 @@ export const Outlet = React.memo(function OutletImpl() {
: undefined

// eslint-disable-next-line react-hooks/rules-of-hooks
;[routeId, parentGlobalNotFound] = useStore(parentMatchStore, (match) => [
match?.routeId as string | undefined,
match?.globalNotFound ?? false,
])
;[routeId, parentGlobalNotFound] = useStore(
parentMatchStore,
(match): OutletMatchSelection => [
match?.routeId as string | undefined,
match?.globalNotFound ?? false,
],
outletMatchSelectionEqual,
)

// eslint-disable-next-line react-hooks/rules-of-hooks
childMatchId = useStore(router.stores.matchesId, (ids) => {
Expand Down
Loading