-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathNavigationProgress.tsx
More file actions
82 lines (70 loc) · 2.18 KB
/
Copy pathNavigationProgress.tsx
File metadata and controls
82 lines (70 loc) · 2.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
'use client'
import { usePathname, useSearchParams } from 'next/navigation'
import {
createContext,
Suspense,
use,
useCallback,
useEffect,
useRef,
useState,
} from 'react'
import Loading from './Loading'
type NavigationProgressContextType = {
start(): void
}
const NavigationProgressContext =
createContext<NavigationProgressContextType | null>(null)
export function useNavigationProgress() {
const context = use(NavigationProgressContext)
if (!context) {
throw new Error(
'useNavigationProgress must be used within <NavigationProgress>',
)
}
return context
}
// Wrapped in Suspense because useSearchParams() requires a Suspense boundary.
function NavigationComplete({ onComplete }: { onComplete: () => void }) {
const pathname = usePathname()
const searchParams = useSearchParams()
const currentUrlRef = useRef(pathname + searchParams.toString())
useEffect(() => {
const newUrl = pathname + searchParams.toString()
if (newUrl !== currentUrlRef.current) {
currentUrlRef.current = newUrl
onComplete()
}
}, [pathname, searchParams, onComplete])
return null
}
// Navigation start is signalled via onNavigate on a <ProgressLink>; completion
// is detected by watching usePathname()/useSearchParams().
export default function NavigationProgress({
children,
}: {
children: React.ReactNode
}) {
const [isRouteChanging, setIsRouteChanging] = useState(false)
const [loadingKey, setLoadingKey] = useState(0)
// Read directly during render to lazily create a stable context value
// once; useRef's initial-value argument is only ever used on the very
// first render.
// eslint-disable-next-line react-hooks/refs
const contextValue = useRef<NavigationProgressContextType>({
start: () => {
setIsRouteChanging(true)
setLoadingKey((prev) => prev ^ 1)
},
}).current
const handleComplete = useCallback(() => setIsRouteChanging(false), [])
return (
<NavigationProgressContext value={contextValue}>
<Loading isRouteChanging={isRouteChanging} key={loadingKey} />
<Suspense>
<NavigationComplete onComplete={handleComplete} />
</Suspense>
{children}
</NavigationProgressContext>
)
}