Maxime Ghéraille
Maxime Ghéraille

Reputation: 1895

listen for router change via useEffect in NextJS

I am trying to get an useEffect to execute some code when a user changes the page via the navigation.

My problem is that the useEffect does not execute on the route change (does not show up in console). It does only show up on the first initial page load and never after

I used the google analytics example from nextJS

This is my simple _app.tsx

import type { AppProps } from "next/app";
import "./styles/css.scss";
import { useRouter } from "next/dist/client/router";
import { useEffect} from "react";



const Child: React.FC = ({ children }) => {
  return (
    <>
      <div>{children}</div>
    </>
  );
};

const App = ({ Component, pageProps }: AppProps) => {
  const router = useRouter();

  useEffect(() => {

    console.log("called");

  }, [router.events]);
  return (
    <>
      <Child>
        <Component {...pageProps} />
      </Child>
    </>
  );
};
export default App;

Upvotes: 25

Views: 27326

Answers (6)

Mahesh Jamdade
Mahesh Jamdade

Reputation: 20231

As of latest NextJS 14, You can listen for page changes by composing other Client Component hooks like usePathname and useSearchParams.


'use client'
 
import { useEffect } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'
 
export function NavigationEvents() {
  const pathname = usePathname()
  const searchParams = useSearchParams()
 
  useEffect(() => {
    const url = `${pathname}?${searchParams}`
    console.log(url)
    // You can now use the current URL
    // ...
  }, [pathname, searchParams])
 
  return null
}

And wrap this component with Suspense

import { Suspense } from 'react'
import { NavigationEvents } from './components/navigation-events'
 
export default function Layout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
      // Wrap the component with Suspense here
        <Suspense fallback={null}>
          <NavigationEvents />
        </Suspense>

      </body>
    </html>
  )
}

source: https://nextjs.org/docs/app/api-reference/functions/use-router#examples

Upvotes: 1

Chukwuemeka Maduekwe
Chukwuemeka Maduekwe

Reputation: 8536

import  { useRouter } from "next/router";
import { useEffect, useState } from "react";

const App = ({ }) => {
  const router = useRouter(),
  [ready, setReady] = useState(false);

  const enableReady = async (loc) => {
  setReady(false);
  console.log("enableReady", loc);
};

const disableReady = async (loc) => {
  console.log(".....");
  await sleep(3);
  console.log("done");
  setReady(true);
  console.log("disableReady", loc);
};

useEffect(() => {
  router.events.on("routeChangeStart", async () => await enableReady("star a"));
  router.events.on("routeChangeComplete", async () => await disableReady("com a"));
  router.events.on("routeChangeError", async () => await disableReady("err a"));

  return () => {
    router.events.off("routeChangeStart", async () => await enableReady("star b"));
    router.events.off("routeChangeComplete", async () => await disableReady("com b"));
    router.events.off("routeChangeError", async () => await disableReady("err b"));
  };
}, [router.events]);

}

Upvotes: 2

Matt Muggeridge
Matt Muggeridge

Reputation: 475

I was able to get this working more simply, by listening for changes to router.asPath.

  useEffect(() => {
    console.log('useEffect fired!', {asPath: router.asPath});
  }, [router.asPath]);

Upvotes: 21

Faruk
Faruk

Reputation: 111

You can use routeChangeStart, routeChangeComplete and routeChangeError events to do some functionality whenever the route is changing.

Here I use NProgress to show slim progress bar, if you want you can try the same thing and get some idea about how its working or you can just log some data, () => { console.log("route change start") }

import '../styles/globals.css'
import 'nprogress/nprogress.css'
import NProgress from 'nprogress'
import { Router } from 'next/dist/client/router'
Router.events.on('routeChangeStart', () => {
  NProgress.start()
})
Router.events.on('routeChangeComplete', () => {
  NProgress.done()
})
Router.events.on('routeChangeError', () => {
  NProgress.done()
})
function MyApp({ Component, pageProps }) {
  return (
    <div>
      <h1 style={{ textAlign: 'center', alignItems: 'center' }}>
        Welcome to my App
      </h1>
      <Component {...pageProps} />
    </div>
  )
}

export default MyApp

Upvotes: 7

Maxime Gh&#233;raille
Maxime Gh&#233;raille

Reputation: 1895

Thank to @Faruk answer, I was able to find that it was possible but I had to use those router.events.on inside my hook.

useEffect(() => {
    router.events.on("routeChangeComplete", () => {
      console.log("route change routeChangeComplete");
    });
    return () => {
      router.events.off("routeChangeComplete", () => {
        console.log("stoped");
      });
    };
  }, [router.events]);

Upvotes: 11

سعيد
سعيد

Reputation: 1764

you can use useLocation for this

const App = ({ Component, pageProps }: AppProps) => {
  let location = useLocation();

  useEffect(() => {

    alert(location );// this will be called whenever the route changes 

  }, [location ]);
  return (
    <>
      <Child>
        <Component {...pageProps} />
      </Child>
    </>
  );
};
export default App;

Upvotes: 0

Related Questions