Home > Software engineering >  Nextjs Warning: Maximum update depth exceeded
Nextjs Warning: Maximum update depth exceeded

Time:12-27

I'm writing a react Header component and have added the functionality to click in and out of a dropdown menu. I'm getting the following error whenever I load the page:

next-dev.js?3515:20 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render

I imagine I'm making a mistake somewhere in a useEffect (I am running 2 useEffects, one inside a component and one inside another function), but am unsure where I'm going wrong. Here is the code below:

  import React from "react";
  import { useState, useEffect, useRef } from "react";
  import { FaChevronDown, FaLastfmSquare } from "react-icons/all";
  import { signOut } from "firebase/auth";
  import { useRouter } from "next/router";
  import { auth } from "../utils/auth";

  function useOutsideAlerter() {
    const [visible, setVisible] = useState(false);
    const ref = useRef(null);

    useEffect(() => {
      const handleHideDropdown = (event) => {
        if (event.key === "Escape") {
          setVisible(false);
        }
      };

      const handleClickOutside = (e) => {
        if (ref.current && !ref.current.contains(e.target)) {
          setVisible(false);
        }
      };

      document.addEventListener("keydown", handleHideDropdown, true);
      document.addEventListener("mousedown", handleClickOutside, true);

      return () => {
        document.removeEventListener("keydown", handleHideDropdown, true);
        document.removeEventListener("mousedown", handleClickOutside, true);
      };
    }, []);

    return { visible, ref, setVisible };
  }

  const Header = () => {
    const [user, setUser] = useState(null);

    const router = useRouter();
    const { ref, visible, setVisible } = useOutsideAlerter();

    useEffect(() => {
      const user = localStorage.getItem("user");
      if (user) {
        setUser(JSON.parse(user));
      }
    });

    const logUserOut = () => {
      signOut(auth)
        .then(() => {
          localStorage.removeItem("user");
          router.push("/");
        })
        .catch((error) => {
          console.error(error);
          alert("There was a problem signing out.");
        });
    };

    return (
      <div className="flex items-center justify-between p-4 border-b border-gray-300">
        <h1 className={"font-bold text-lg"}>Matrice</h1>
        <div ref={ref}>
          <button
            onClick={() => setVisible(!visible)}
            className="hover:bg-gray-100 rounded-md p-2 flex items-center justify-center text-gray-400"
          >
            {user?.displayName || "Login"} <FaChevronDown className={"ml-2"} />
          </button>
          {visible && (
            <div
              className={
                "absolute top-14 right-4 rounded-md border border-gray-300 bg-white overflow-hidden"
              }
            >
              {user && (
                <button
                  onClick={logUserOut}
                  className={
                    "text-left text-sm hover:bg-blue-700 hover:text-white w-full px-4 py-2"
                  }
                >
                  Log Out
                </button>
              )}
              <button
                onClick={() =>
                  router.replace(
                    "mailto:email"
                  )
                }
                className={
                  "text-left text-sm hover:bg-blue-700 hover:text-white w-full px-4 py-2"
                }
              >
                Request Feature
              </button>
            </div>
          )}
        </div>
      </div>
    );
  };

  export default Header;


CodePudding user response:

I imagine I'm making a mistake somewhere in a useEffect

Yes, you haven't specified a dependency array for your useEffect() within your Header component:

useEffect(() => {
  const user = localStorage.getItem("user");
  if (user) {
    setUser(JSON.parse(user));
  }
}, []); // <--- added empty dependency array

Without a dependeency array, your useEffect() callback will run after every render/rerender. So in your current code, this happens:

  1. React renders your JSX
  2. Your useEffect() callback is called, calling setUser if the user key in your local storage is set
  3. As setUser() was called with a new object reference (a new object is created when doing JSON.parse()), React sees this as an update and triggers a rerennder, and so we go back to step 1 above, causing an infinite loop.

By adding an empty dependency array [] to the useEffect() call, you're telling React to only call your function on the initial mount, and not subsequent rerenders.

  • Related