useCrossCompatRouter.tsx65 lines · main
1'use client'
2
3import { useRouter as useLegacyRouter } from 'next/compat/router'
4import { useRouter as useNextRouter } from 'next/navigation'
5import { createContext, useContext, useMemo, useRef, useTransition } from 'react'
6
7type Handler = (...evts: any[]) => void
8
9export const CrossCompatRouterContext = createContext<{ onPendingEnd: Set<Handler> } | undefined>(
10 undefined
11)
12
13function useCrossCompatRouterContext() {
14 const ctx = useContext(CrossCompatRouterContext)
15 if (!ctx) {
16 throw Error(
17 'useCrossCompatRouterContext must be used within a CrossCompatRouterContext.Provider'
18 )
19 }
20 return ctx
21}
22
23function useHasChanged<T>(value: T, { onlyTo }: { onlyTo?: T } = {}) {
24 const prev = useRef(value)
25
26 let result = true
27
28 if (prev.current === value) result = false
29 if (onlyTo && value !== onlyTo) result = false
30
31 prev.current = value
32
33 return result
34}
35
36export function useCrossCompatRouter() {
37 const { onPendingEnd } = useCrossCompatRouterContext()
38
39 const legacyRouter = useLegacyRouter()
40 const newRouter = useNextRouter()
41 const isUsingLegacyRouting = !!legacyRouter
42
43 const [isPending, startTransition] = useTransition()
44 const hasPendingEnded = useHasChanged(isPending, { onlyTo: false })
45 if (!isUsingLegacyRouting && hasPendingEnded) {
46 onPendingEnd.forEach((fn) => fn())
47 }
48
49 const api = useMemo(
50 () => ({
51 push: (path: string) => {
52 if (isUsingLegacyRouting) {
53 legacyRouter.push(path)
54 } else {
55 startTransition(() => {
56 newRouter.push(path)
57 })
58 }
59 },
60 }),
61 [isUsingLegacyRouting, legacyRouter, newRouter]
62 )
63
64 return api
65}