11"use client" ;
22
33// eslint-disable-next-line no-restricted-imports
4- import { usePathname , useRouter , useSearchParams } from "next/navigation" ;
4+ import { useRouter } from "next/navigation" ;
55import {
6- Suspense ,
6+ type TransitionStartFunction ,
77 useEffect ,
88 useMemo ,
99 useRef ,
1010 useState ,
1111 useSyncExternalStore ,
12+ useTransition ,
1213} from "react" ;
1314import { createStore } from "./reactive" ;
1415
1516// Using useDashboardRouter instead of useRouter gives us a nice progress bar on top of the page when navigating using router.push or router.replace
1617
17- // using a store instead of context to avoid triggering re-renders on root component
18- const LoadingRouteHref = createStore < string | undefined > ( undefined ) ;
18+ const RouteStartTransitionFn = createStore < TransitionStartFunction > ( ( fn ) =>
19+ fn ( ) ,
20+ ) ;
1921
2022export function useDashboardRouter ( ) {
23+ const startTransition = useSyncExternalStore (
24+ RouteStartTransitionFn . subscribe ,
25+ RouteStartTransitionFn . getValue ,
26+ RouteStartTransitionFn . getValue ,
27+ ) ;
28+
2129 const router = useRouter ( ) ;
2230 return useMemo ( ( ) => {
2331 return {
2432 ...router ,
2533 replace ( href : string , options ?: { scroll ?: boolean } ) {
26- LoadingRouteHref . setValue ( href ) ;
27- router . replace ( href , options ) ;
34+ startTransition ( ( ) => {
35+ router . replace ( href , options ) ;
36+ } ) ;
2837 } ,
2938 push ( href : string , options ?: { scroll ?: boolean } ) {
30- LoadingRouteHref . setValue ( href ) ;
31- router . push ( href , options ) ;
39+ startTransition ( ( ) => {
40+ router . replace ( href , options ) ;
41+ } ) ;
3242 } ,
3343 } ;
34- } , [ router ] ) ;
44+ } , [ router , startTransition ] ) ;
3545}
3646
37- function useRouterLoadingStatus ( ) {
38- const loadingHref = useSyncExternalStore (
39- LoadingRouteHref . subscribe ,
40- LoadingRouteHref . getValue ,
41- LoadingRouteHref . getValue ,
42- ) ;
43- const pathname = usePathname ( ) ;
44- const searchParams = useSearchParams ( ) ;
45- const searchParamsStr = searchParams ?. toString ( ) ;
46-
47- const routerHref = pathname + ( searchParamsStr ? `?${ searchParamsStr } ` : "" ) ;
48- const isLoading = loadingHref && loadingHref !== routerHref ;
47+ const wait = ( ms : number ) => new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
4948
50- // reset loading on route load
49+ export function DashboardRouterTopProgressBar ( ) {
50+ const [ isRouteLoading , startTransition ] = useTransition ( ) ;
5151 // eslint-disable-next-line no-restricted-syntax
5252 useEffect ( ( ) => {
53- if ( ! isLoading ) {
54- LoadingRouteHref . setValue ( undefined ) ;
55- }
56- } , [ isLoading ] ) ;
57-
58- return isLoading ;
59- }
53+ RouteStartTransitionFn . setValue ( startTransition ) ;
54+ } , [ ] ) ;
6055
61- const wait = ( ms : number ) => new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
62-
63- // Render this on root
64- function DashboardRouterTopProgressBarInner ( ) {
65- const isLoading = useRouterLoadingStatus ( ) ;
6656 const [ progress , setProgress ] = useState ( 0 ) ;
67-
6857 const progressStartedRef = useRef ( false ) ;
6958
7059 // eslint-disable-next-line no-restricted-syntax
7160 useEffect ( ( ) => {
72- if ( ! isLoading ) {
61+ if ( ! isRouteLoading ) {
7362 setProgress ( 0 ) ;
7463 progressStartedRef . current = false ;
7564 return ;
@@ -85,7 +74,7 @@ function DashboardRouterTopProgressBarInner() {
8574
8675 async function updateProgressBar ( progress : number , delay : number ) {
8776 if ( ! isMounted ) {
88- if ( ! isLoading ) {
77+ if ( ! isRouteLoading ) {
8978 setProgress ( 100 ) ;
9079 }
9180 return ;
@@ -112,14 +101,14 @@ function DashboardRouterTopProgressBarInner() {
112101 return ( ) => {
113102 isMounted = false ;
114103 } ;
115- } , [ isLoading ] ) ;
104+ } , [ isRouteLoading ] ) ;
116105
117- const width = isLoading ? progress : 100 ;
106+ const width = isRouteLoading ? progress : 100 ;
118107 return (
119108 < span
120109 className = "fixed top-0 block h-[3px] bg-foreground"
121110 style = { {
122- opacity : isLoading ? "100" : "0" ,
111+ opacity : isRouteLoading ? "100" : "0" ,
123112 width : `${ width } %` ,
124113 transition : width === 0 ? "none" : "width 0.2s ease, opacity 0.3s ease" ,
125114 zIndex : "100000000" ,
@@ -128,12 +117,3 @@ function DashboardRouterTopProgressBarInner() {
128117 />
129118 ) ;
130119}
131-
132- // need to wrap with suspense because of useSearchParams usage
133- export function DashboardRouterTopProgressBar ( ) {
134- return (
135- < Suspense fallback = { null } >
136- < DashboardRouterTopProgressBarInner />
137- </ Suspense >
138- ) ;
139- }
0 commit comments