javascriptreactjsreact-hooksreact-routerreact-router-dom

React Router useNavigate with a useEffect hook - proper way to use?


I'm new to React and trying to make a loading/greeting page that navigates on to the next after a few seconds of being shown. In React Router v6, we have the useNavigate() hook to allow you to control the navigation, and I am using this to successfully call the navigate function by setting a timeout in a useEffect() hook. However, the compiler is complaining that I have a missing dependency. I only want it to run once though, not whenever the navigate changes. What is the best way to do this?

Thanks!

import { useEffect } from "react";
import { useNavigate } from "react-router-dom";

function Greeting(props) {
  const navigate = useNavigate();
  useEffect(() => {
    setTimeout(() => navigate(props.nextPage), 3000);
  }, []);

  return (
    <div className="Greeting">
      <div>Hello World!</div>
    </div>
  );
}

export default Greeting;

Line 9:6: React Hook useEffect has a missing dependency: 'navigate'. Either include it or remove the dependency array react-hooks/exhaustive-deps


Solution

  • The useEffect hook is missing dependencies, both the navigate function and props.nextPage that are referenced in the callback. The linter warning is informing you to add them to the dependency array. The navigate function is an external reference (to the effect) so it should be included as a dependency, so this leaves the nextPage prop value that should also be included so it's re-enclosed in the timeout callback. Don't forget to return a cleanup function in the case that the component unmounts prior to the timeout expiring on its own.

    useEffect(() => {
      const timerId = setTimeout(() => navigate(props.nextPage), 3000);
      return () => clearTimeout(timerId);
    }, [navigate, props.nextPage]);
    

    As a general rule you should follow all guidance from the linter. The react-hooks/exhaustive-deps rule is there to help you write better code. Don't disable the rule for that line unless you are absolutely sure and are aware of what future consequences may arise if/when you ever update the callback logic. Disabling the linter rule will potentially mask future issues if the implementation changes and you do actually want to run the effect more often. My rule for this occasion is to add a comment to my future self and other team members to be aware they should check the dependency array.

    Example:

    useEffect(() => {
      const timerId = setTimeout(() => navigate(props.nextPage), 3000);
     
      return () => clearTimeout(timerId);
     
      // NOTE: Run effect once on component mount. Re-check dependencies
      // if effect logic is updated.
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);