next.js icon indicating copy to clipboard operation
next.js copied to clipboard

Docs: How to migrate from async router.push to new `next/navigation` push method

Open ajwootto opened this issue 1 year ago • 7 comments

What is the improvement or update you wish to see?

The new router in next/navigation no longer returns a promise from the push method. From what I've been able to understand, this is so that it can work with the startTransition 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 call

Is 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-hooks

This 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

ajwootto avatar Feb 06 '24 20:02 ajwootto

Here here, also need it badly.

Guo-dalu avatar Feb 07 '24 10:02 Guo-dalu

Agreed. We built our own hacky solution to await router pushes, but would really like built-in support for this!

arcticfly avatar May 22 '24 20:05 arcticfly

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

ArianHamdi avatar May 22 '24 21:05 ArianHamdi

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 <RouteType>(href: NextRouteTypes.RouteImpl<RouteType>) => {
      router.push(href as any);

      await new Promise<void>((resolve) => {
        // wait for the route change to complete
        setResolveRouteChanged(resolve);
      });
    };

    return { ...router, push };
  }, [router]);

  return asyncRouter;
};

arcticfly avatar May 22 '24 22:05 arcticfly

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>
}

Cauen avatar Jul 24 '24 20:07 Cauen

This should be mentioned in the documentation as a breaking change as there's no built-in support for this feature now.

allicanseenow avatar Sep 12 '24 03:09 allicanseenow

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

image

TradeeHubDev avatar Nov 12 '24 20:11 TradeeHubDev

+1 This feature is really needed. It's been a year with no response? I don't know why the Next.js team prioritizes RSCs and server stuff while neglecting important features like these.

HasanMothaffar avatar Feb 19 '25 11:02 HasanMothaffar

Still nothing? Is something what is keeping it back from implementing this in app router?

scyllacz avatar Sep 17 '25 23:09 scyllacz

I would love it implemented too, it's a bummer that we have to do it manually

lucastnr avatar Oct 02 '25 17:10 lucastnr

Same here, not enjoying writing hacks to get around the fact that router.push isn't async

daveidivide avatar Oct 09 '25 08:10 daveidivide