9

With the old pages router, I could await router.push. I used this to keep a button disabled while we are in the process of navigating to another page:

 async function onSubmit(input: CreatePostFormData) {
 try {
 await BlogApi.createBlogPost(input);
 await router.push("/blog/" + slug);
 } catch (error) {
 console.error(error);
 alert(error);
 }
 }

The new router doesn't return a Promise. Is there a way to achieve a similar behavior without using my own state?

asked May 15, 2023 at 11:06
1
  • 1
    I cannot believe something super simple such as waiting for navigation to actually perform something is broken in Next.js. Commented May 18, 2024 at 11:21

2 Answers 2

1

An alternative version of @ErickLee response

'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>
}
answered Jul 24, 2024 at 20:18
Sign up to request clarification or add additional context in comments.

Comments

-1

In the past, there was a capability where you could wait for a route transition to complete. However, due to some changes or limitations, this capability is no longer available.

As an alternative, the suggestion is to attempt wrapping the call to router.push (a method that triggers a route transition) with a startTransition hook. Here it is an example:

import { useRouter } from 'next/navigation';
import { useEffect, useState, useTransition } from 'react';
export const useRouterAsync = () => {
 const [isLoadingRouter, setIsLoadingRouter] = useState(true);
 const [isPending, startTransition] = useTransition();
 const router = useRouter();
 const handleRoute = async (path: string) => {
 // Note: utilize startTransition because router.push no longer returns a promise.
 startTransition(() => {
 router.push(path);
 });
 };
 useEffect(() => {
 if (isPending) {
 return setIsLoadingRouter(true);
 }
 setIsLoadingRouter(false);
 }, [isPending]);
 return { handleRoute, isLoadingRouter };
};

NOTE: There's a concern that because the page will probably be unmounted during the transition, certain states or variables, like isPending, may be lost. The consequence of the page unmounting is that the isPending state (likely indicating whether the route transition is pending or not) would be lost, which could be problematic for the functionality of the code.

answered Feb 3, 2024 at 9:08

1 Comment

what do we gain? handleRoute() returns before the push is complete

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.