Search code examples
reactjsnext.jsnext-router

Router events cause an error while being used on the constructor Warning: Can't perform a React state update on an unmounted component


I'm facing this issue, I tried to convert it to a function but the states didn't work as I expected. How can I solve this? I want to create a loading effect when switching routes without causing such issues:

react-dom.development.js?ac89:67 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
        at Loading (webpack-internal:///./components/loading.js:83:9)
        at MyApp (webpack-internal:///./pages/_app.js:58:27)
        at StyleRegistry (webpack-internal:///./node_modules/styled-jsx/dist/stylesheet-registry.js:231:34)

here is my code:

import Router from "next/router";
import { Component } from "react";
import Loader from "./Loader";

export default class Loading extends Component {
  constructor() {
      super();
      this.state = {
        loader: false
      }
  }

  componentDidMount(){
      // Some page has started loading
      Router.events.on("routeChangeStart", (url) => {
        this.setState({ loader: true });
      });
         // Some page has finished loading
         Router.onRouteChangeComplete = (url) => {
          this.setState({ loader: false });
        };
  }
  componentWillUnmount(){

  }  

  render() {
    return(
      <Loader loading={this.state.loader} />
    );
  }
}

I also tried:

import Router from "next/router";
import { useState } from "react";
import Loader from "./Loader";

export default function Loading (){
  const [loader, setLoader] = useState(false);

  // Some page has started loading
  Router.events.on("routeChangeStart", (url) => {
      setLoader(true);
  });
  // Some page has finished loading
  Router.onRouteChangeComplete = (url) => {
      setLoader(false);
  };

  return <Loader loading={loader} />;
}

Solution

  • The code you had in componentDidMount needs to be moved inside a useEffect. In addition, the subscriptions to the router events need to be cleaned up when the component unmounts to prevent the error you're seeing.

    It's also recommended to use the router instance from the useRouter hook rather than accessing the global router object directly.

    Your function component should roughly look like the following.

    import { useRouter } from "next/router";
    import { useEffect, useState } from "react";
    import Loader from "./Loader";
    
    export default function Loading () {
        const [loader, setLoader] = useState(false);
        const router = useRouter();
    
        useEffect(() => {
            const handleRouteChangeStart = (url) => {
                setLoader(true);
            };
            const handleRouteChangeComplete = (url) => {
                setLoader(false);
            };
    
            router.events.on("routeChangeStart", handleRouteChangeStart);
            router.events.on("routeChangeComplete", handleRouteChangeComplete);
    
            // When the component unmounts, unsubscribe from the router events with the `off` method
            return () => {
                router.events.off("routeChangeStart", handleRouteChangeStart);
                router.events.off("routeChangeComplete", handleRouteChangeComplete);
            };
        }, [router]);
      
        return <Loader loading={loader} />;
    }