131 lines
3.4 KiB
Plaintext
131 lines
3.4 KiB
Plaintext
import * as React from 'react'
|
|
import {
|
|
getLocationChangeInfo,
|
|
handleHashScroll,
|
|
trimPathRight,
|
|
} from '@tanstack/router-core'
|
|
import { useLayoutEffect, usePrevious } from './utils'
|
|
import { useRouter } from './useRouter'
|
|
import { useRouterState } from './useRouterState'
|
|
|
|
export function Transitioner() {
|
|
const router = useRouter()
|
|
const mountLoadForRouter = React.useRef({ router, mounted: false })
|
|
const isLoading = useRouterState({
|
|
select: ({ isLoading }) => isLoading,
|
|
})
|
|
|
|
const [isTransitioning, setIsTransitioning] = React.useState(false)
|
|
// Track pending state changes
|
|
const hasPendingMatches = useRouterState({
|
|
select: (s) => s.matches.some((d) => d.status === 'pending'),
|
|
structuralSharing: true,
|
|
})
|
|
|
|
const previousIsLoading = usePrevious(isLoading)
|
|
|
|
const isAnyPending = isLoading || isTransitioning || hasPendingMatches
|
|
const previousIsAnyPending = usePrevious(isAnyPending)
|
|
|
|
const isPagePending = isLoading || hasPendingMatches
|
|
const previousIsPagePending = usePrevious(isPagePending)
|
|
|
|
if (!router.isServer) {
|
|
router.startTransition = (fn: () => void) => {
|
|
setIsTransitioning(true)
|
|
React.startTransition(() => {
|
|
fn()
|
|
setIsTransitioning(false)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Subscribe to location changes
|
|
// and try to load the new location
|
|
React.useEffect(() => {
|
|
const unsub = router.history.subscribe(router.load)
|
|
|
|
const nextLocation = router.buildLocation({
|
|
to: router.latestLocation.pathname,
|
|
search: true,
|
|
params: true,
|
|
hash: true,
|
|
state: true,
|
|
_includeValidateSearch: true,
|
|
})
|
|
|
|
if (
|
|
trimPathRight(router.latestLocation.href) !==
|
|
trimPathRight(nextLocation.href)
|
|
) {
|
|
router.commitLocation({ ...nextLocation, replace: true })
|
|
}
|
|
|
|
return () => {
|
|
unsub()
|
|
}
|
|
}, [router, router.history])
|
|
|
|
// Try to load the initial location
|
|
useLayoutEffect(() => {
|
|
if (
|
|
(typeof window !== 'undefined' && router.clientSsr) ||
|
|
(mountLoadForRouter.current.router === router &&
|
|
mountLoadForRouter.current.mounted)
|
|
) {
|
|
return
|
|
}
|
|
mountLoadForRouter.current = { router, mounted: true }
|
|
|
|
const tryLoad = async () => {
|
|
try {
|
|
await router.load()
|
|
} catch (err) {
|
|
console.error(err)
|
|
}
|
|
}
|
|
|
|
tryLoad()
|
|
}, [router])
|
|
|
|
useLayoutEffect(() => {
|
|
// The router was loading and now it's not
|
|
if (previousIsLoading && !isLoading) {
|
|
router.emit({
|
|
type: 'onLoad', // When the new URL has committed, when the new matches have been loaded into state.matches
|
|
...getLocationChangeInfo(router.state),
|
|
})
|
|
}
|
|
}, [previousIsLoading, router, isLoading])
|
|
|
|
useLayoutEffect(() => {
|
|
// emit onBeforeRouteMount
|
|
if (previousIsPagePending && !isPagePending) {
|
|
router.emit({
|
|
type: 'onBeforeRouteMount',
|
|
...getLocationChangeInfo(router.state),
|
|
})
|
|
}
|
|
}, [isPagePending, previousIsPagePending, router])
|
|
|
|
useLayoutEffect(() => {
|
|
// The router was pending and now it's not
|
|
if (previousIsAnyPending && !isAnyPending) {
|
|
router.emit({
|
|
type: 'onResolved',
|
|
...getLocationChangeInfo(router.state),
|
|
})
|
|
|
|
router.__store.setState((s) => ({
|
|
...s,
|
|
status: 'idle',
|
|
resolvedLocation: s.location,
|
|
}))
|
|
|
|
handleHashScroll(router)
|
|
}
|
|
}, [isAnyPending, previousIsAnyPending, router])
|
|
|
|
return null
|
|
}
|