Shallow router push in Next.js 13 with appDir enabled
Asked Answered
G

3

6

In < Next 13 (or with appDir disabled), you could do:

const MyComponent = () => {

  const router = useRouter();

  const toggleStatic = () => {  
    if (router.query.static) {
      router.push(router.pathname, router.pathname, { shallow: true });
    } else {
      router.push(router.pathname, router.pathname + "?static", { shallow: true });
    }
  }

  return <>
    // ...
  </>

});

This would perform a shallow router update that would change the location, but not push it to history or trigger a page load.

Now, with appDir enabled, you need to import functions from next/navigation instead. But the docs don't say anything about shallow router pushing using the new router?

All I can do is this:

const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();

const toggleStatic = () => {
  if (searchParams.get("static")) {
    router.push(pathname);
  } else {
    router.push(pathname + "?static");
  }
};

But that does a full page reload. Is there a way to replicate the shallow router functionality using Next 13's appDir?

Gio answered 13/4, 2023 at 15:55 Comment(1)
see github.com/vercel/next.js/discussions/48110Discontent
B
1

Try the replace method instead of push:

const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();

const toggleStatic = () => {
  if (searchParams.get("static")) {
    router.replace(pathname);
  } else {
    router.replace(pathname + "?static");
  }
};

useRouter in nextjs docs.

Beecham answered 4/6, 2023 at 7:26 Comment(1)
Downside to this is it then doesn't update the history. Shallow routing updated history alongside the page draw.Gio
M
0

I wrote a custom hooks that solves it in my case:

export function useShallowNavigation(defaultValues: Record<string, unknown> = {}) {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const [params, setParams] = useState(
    Object.entries(defaultValues).reduce((agg, [key, value]) => {
      if (!agg[key]) agg[key] = String(value);
      return agg;
    }, Object.fromEntries(searchParams.entries()))
  );

  useEffect(() => {
    const newUrl = Object.keys(params).length
      ? `${pathname}?${new URLSearchParams(params).toString()}`
      : pathname;

    if (newUrl !== window.location.pathname + window.location.search) {
      window.history.pushState(
        { ...window.history.state, as: newUrl, url: newUrl },
        '',
        newUrl
      );
    }
  }, [pathname, params]);

  return [params, setParams] as const;
}

The key is to handle the query parameters in an object state, and then sync the url from that state.

Mightily answered 26/3 at 7:50 Comment(0)
R
-1

Try to add scroll option to router.push { scroll: false }.

router.push('/updated-route', { scroll: false });

Documentation: https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#disabling-scroll-restoration

Also you can check this thread: Next.js - router.push without scrolling to the top

Receivership answered 26/3 at 7:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.