140 lines
3.8 KiB
Plaintext
140 lines
3.8 KiB
Plaintext
'use client'
|
|
|
|
import * as React from 'react'
|
|
import { batch, useStore } from '@tanstack/react-store'
|
|
import {
|
|
getLocationChangeInfo,
|
|
handleHashScroll,
|
|
trimPathRight,
|
|
} from '@tanstack/router-core'
|
|
import { useLayoutEffect, usePrevious } from './utils'
|
|
import { useRouter } from './useRouter'
|
|
|
|
export function Transitioner() {
|
|
const router = useRouter()
|
|
const mountLoadForRouter = React.useRef({ router, mounted: false })
|
|
|
|
const [isTransitioning, setIsTransitioning] = React.useState(false)
|
|
// Track pending state changes
|
|
const isLoading = useStore(router.stores.isLoading, (value) => value)
|
|
const hasPending = useStore(router.stores.hasPending, (value) => value)
|
|
|
|
const previousIsLoading = usePrevious(isLoading)
|
|
|
|
const isAnyPending = isLoading || isTransitioning || hasPending
|
|
const previousIsAnyPending = usePrevious(isAnyPending)
|
|
|
|
const isPagePending = isLoading || hasPending
|
|
const previousIsPagePending = usePrevious(isPagePending)
|
|
|
|
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,
|
|
})
|
|
|
|
// Check if the current URL matches the canonical form.
|
|
// Compare publicHref (browser-facing URL) for consistency with
|
|
// the server-side redirect check in router.beforeLoad.
|
|
if (
|
|
trimPathRight(router.latestLocation.publicHref) !==
|
|
trimPathRight(nextLocation.publicHref)
|
|
) {
|
|
router.commitLocation({ ...nextLocation, replace: true })
|
|
}
|
|
|
|
return () => {
|
|
unsub()
|
|
}
|
|
}, [router, router.history])
|
|
|
|
// Try to load the initial location
|
|
useLayoutEffect(() => {
|
|
if (
|
|
// if we are hydrating from SSR, loading is triggered in ssr-client
|
|
(typeof window !== 'undefined' && router.ssr) ||
|
|
(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.stores.location.get(),
|
|
router.stores.resolvedLocation.get(),
|
|
),
|
|
})
|
|
}
|
|
}, [previousIsLoading, router, isLoading])
|
|
|
|
useLayoutEffect(() => {
|
|
// emit onBeforeRouteMount
|
|
if (previousIsPagePending && !isPagePending) {
|
|
router.emit({
|
|
type: 'onBeforeRouteMount',
|
|
...getLocationChangeInfo(
|
|
router.stores.location.get(),
|
|
router.stores.resolvedLocation.get(),
|
|
),
|
|
})
|
|
}
|
|
}, [isPagePending, previousIsPagePending, router])
|
|
|
|
useLayoutEffect(() => {
|
|
if (previousIsAnyPending && !isAnyPending) {
|
|
const changeInfo = getLocationChangeInfo(
|
|
router.stores.location.get(),
|
|
router.stores.resolvedLocation.get(),
|
|
)
|
|
router.emit({
|
|
type: 'onResolved',
|
|
...changeInfo,
|
|
})
|
|
|
|
batch(() => {
|
|
router.stores.status.set('idle')
|
|
router.stores.resolvedLocation.set(router.stores.location.get())
|
|
})
|
|
|
|
if (changeInfo.hrefChanged) {
|
|
handleHashScroll(router)
|
|
}
|
|
}
|
|
}, [isAnyPending, previousIsAnyPending, router])
|
|
|
|
return null
|
|
}
|