Open ajwootto opened 9 months ago
Here here, also need it badly.
Agreed. We built our own hacky solution to await router pushes, but would really like built-in support for this!
Agreed. We built our own hacky solution to await router pushes, but would really like built-in support for this!
Can you please share your workaround
Agreed. We built our own hacky solution to await router pushes, but would really like built-in support for this!
Can you please share your workaround
// modify router so that it awaits the route change before returning export const useAsyncRouter = () => { const router = useRouter(); const pathname = usePathname(); const [resolveRouteChanged, setResolveRouteChanged] = useState<(() => void) | null>(null);
useEffect(() => { resolveRouteChanged?.(); }, [pathname]);
const asyncRouter = useMemo(() => {
const push = async
await new Promise<void>((resolve) => {
// wait for the route change to complete
setResolveRouteChanged(resolve);
});
};
return { ...router, push };
}, [router]);
return asyncRouter; };
Here are an alternative implementation of https://stackoverflow.com/a/77931487/6638583 workaround code
'use client'
import { useRouter } from 'next/navigation'
import { useEffect, useTransition } from 'react'
// Define the type for the observer callback function
type ObserverCallback = () => void
const createRouteObserver = () => {
let observer: ObserverCallback | null = null
const setObserver = (callback: ObserverCallback) => {
observer = callback
}
const notify = () => {
if (observer) {
observer()
}
}
return { setObserver, notify }
}
const routeObserver = createRouteObserver()
export const useAsyncRoutePush = () => {
const [isPending, startTransition] = useTransition()
const router = useRouter()
const asynPush = async (path: string) => {
return new Promise<void>((resolve) => {
startTransition(() => {
router.push(path)
})
routeObserver.setObserver(() => {
resolve()
})
})
}
useEffect(() => {
if (!isPending) {
routeObserver.notify()
}
}, [isPending])
return asynPush
}
Usage:
export default function MyComponent() {
const asyncPush = useAsyncRoutePush()
return <Button onClick={async () => {
await asyncPush('/')
console.log("Route changed")
}}>
Go / route
</Button>
}
This should be mentioned in the documentation as a breaking change as there's no built-in support for this feature now.
This feature is really needed because I have an MUI grid which I added custom url tracking of the page in the url via push and when user clicks so fast since it's async all of it that means the router.push is not being awaited and clicking too fast is causing a crash look below
What is the improvement or update you wish to see?
The new router in
next/navigation
no longer returns a promise from thepush
method. From what I've been able to understand, this is so that it can work with thestartTransition
feature of React, but the Next docs don't contain any information about this or how to successfully replace any functionality that was relying on "awaiting" a router.push callIs there any context that might help us understand?
The docs for the push method are currently one sentence here: https://nextjs.org/docs/app/api-reference/functions/use-router#userouter
The migration guide does not mention the fact that the method is no longer async or describe how a migration should take place to the new
push
https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration#step-5-migrating-routing-hooksThis is the one place I've been able to find mention of how it can be used with useTransition, although it also doesn't seem to trigger the pending state in many cases (maybe it just doesn't work in page router?) https://github.com/vercel/next.js/discussions/49810
Does the docs page already exist? Please link to it.
https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration#step-5-migrating-routing-hooks