简体   繁体   中英

useNavigate hook called inside useEffect causing infinite loop

i am using the useNavigate hook inside of react to direct users to a home page after successful login. everything i've read online suggests that i should use the useNavigate hook inside of a useEffect block, but when i do so, it triggers an infinite loop: 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. 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.

the odd thing is, though, that the warning only seems to occur after logging in for a second time. what i mean is that it will work on the first login after starting the server & client, but logging in a second, third, etc. time results in a Maximum update depth exceeded warning.

i use the useNavigate hook in two separate files, a login for users and a login for admin, both of which are below.

for further clarification on the issue arising only on the second login and beyond, it occurs regardless of whether i log in as admin or as a user. for instance, if my first login is as an admin, the Maximum update depth exceeded warning will occur regardless of whether my next login is as a user or as an admin. the same is true if my first login is as a user.

what i've tried so far:

  • removing navigate as from the dependency arrays (results in same warning)
  • removing the useEffect blocks (results in warnings to put the useNavigate hook inside of a useEffect hook. the login functionality breaks)
  • removing the dependency array altogether (results in the same warning)

the error when i login as user:

react-dom.development.js:86 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.
    at Navigate (http://localhost:3000/static/js/bundle.js:42950:5)
    at Routes (http://localhost:3000/static/js/bundle.js:43067:5)
    at div
    at Router (http://localhost:3000/static/js/bundle.js:43000:15)
    at BrowserRouter (http://localhost:3000/static/js/bundle.js:41809:5)
    at App

the error when i login as admin:

react-dom.development.js:86 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.
    at Navigate (http://localhost:3000/static/js/bundle.js:42950:5)
    at Routes (http://localhost:3000/static/js/bundle.js:43067:5)
    at div
    at Router (http://localhost:3000/static/js/bundle.js:43000:15)
    at BrowserRouter (http://localhost:3000/static/js/bundle.js:41809:5)
    at App

Login.jsx

import { useState, useEffect } from "react";
import axios from "axios";
import { Link, useNavigate} from "react-router-dom";


const Login = () => {
  const navigate = useNavigate();

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");


  useEffect(() => {
    if (localStorage.getItem("authToken")) {
      navigate("/home");
    }
}, [navigate]);

  const loginHandler = async (e) => {
    e.preventDefault();

    const config = {
      header: {
        "Content-Type": "application/json",
      },
    };

    try {
      const { data } = await axios.post("/api/auth/login", { email, password }, config);

      localStorage.setItem("authToken", data.token);

      navigate("/home");
      
    } catch (error) {
      setError(error.response.data.error);
      setTimeout(() => {
        setError("");
      }, 5000);
    }
  };

  return (
    <div className="login-">
      <form onSubmit={loginHandler} className="login-__form">
        <h3 className="login-__title">Login</h3>
        {error && <span className="error-message">{error}</span>}
        <div className="form-group">
          <label htmlFor="email">Email:</label>
          <input
            type="email"
            required
            id="email"
            placeholder="Email address"
            onChange={(e) => setEmail(e.target.value)}
            value={email}
            tabIndex={1}
          />
        </div>
        <div className="form-group">
          <label htmlFor="password">
            Password:{" "}
            {/* <Link to="/forgotpassword" className="login-__forgotpassword">
              Forgot Password?
            </Link> */}
          </label>
          <input
            type="password"
            required
            id="password"
            autoComplete="true"
            placeholder="Enter password"
            onChange={(e) => setPassword(e.target.value)}
            value={password}
            tabIndex={2}
          />
        </div>
        <button type="submit" className="btn btn-primary">
          Login
        </button>

        <span className="login-__subtext">
          Don't have an account? <Link to="/register">Register</Link>
        </span>
      </form>
    </div>
  );
};

export default Login;

AdminLogin.jsx

import { useState, useEffect } from "react";
import axios from "axios";
import { Link, useNavigate} from "react-router-dom";


const AdminLogin = () => {
  const navigate = useNavigate();

  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");

  useEffect(() => {
    if (localStorage.getItem("adminToken")) {
      navigate("/admin/home");
    }
  }, [navigate]);

  const adminLoginHandler = async (e) => {
    e.preventDefault();

    const config = {
      header: {
        "Content-Type": "application/json",
      },
    };

    try {
      const { data } = await axios.post("/api/admin/login", { username, password }, config);

      localStorage.setItem("adminToken", data.token);

      navigate("/admin/home");

    } catch (error) {
      setError(error.response.data.error);
      setTimeout(() => {
        setError("");
      }, 5000);
    }
  };

  return (
    <div className="login-">
      <form onSubmit={adminLoginHandler} className="login-__form">
        <h3 className="login-__title">admin login</h3>
        {error && <span className="error-message">{error}</span>}
        <div className="form-group">
          <label htmlFor="username">Username:</label>
          <input
            type="username"
            required
            id="username"
            placeholder="Username"
            onChange={(e) => setUsername(e.target.value)}
            value={username}
            tabIndex={1}
          />
        </div>
        <div className="form-group">
          <label htmlFor="password">
            Password:{" "}
            {/* <Link to="/forgotpassword" className="login-__forgotpassword">
              Forgot Password?
            </Link> */}
          </label>
          <input
            type="password"
            required
            id="password"
            autoComplete="true"
            placeholder="Enter password"
            onChange={(e) => setPassword(e.target.value)}
            value={password}
            tabIndex={2}
          />
        </div>
        <button type="submit" className="btn btn-primary">
          Login
        </button>

        <span className="login-__subtext">
          Here by mistake? <Link to="/login">Login</Link>
        </span>
      </form>
    </div>
  );
};

export default AdminLogin;

it was asked in a comment, so i've also included Private.jsx (user home page) Admin.jsx (admin home page), though i don't think the issue is in either of these files. Private.jsx

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


const Private = () => {
  const [error, setError] = useState("");
  const [privateData, setPrivateData] = useState("");
  
  const navigate = useNavigate();
  


  const handleLogout = () => {
      localStorage.removeItem('authToken');
      navigate('/login');
      console.log('logged out successfully');
  }
  return error ? (
    <span className="error-message">{error}</span>
  ) : (
    <div>
      you're in the private route
      <button onClick = {handleLogout}>Logout</button>
    </div>
  );
};

export default Private;

Admin.jsx

import { useNavigate } from 'react-router-dom';

export default function Admin() {
    const navigate = useNavigate();
    
    const handleLogout = () => {
        localStorage.removeItem('adminToken');
        navigate('/admin/login');
        console.log('logged out successfully');
    }

    return (
        <>
            <div>ADMIN ROUTE UNLOCKED</div>
            <button onClick = {handleLogout}>Logout</button>
        </>
    )
}

You need to listen for changes in your localStorage. Try this:

const checkAuth = () => {
  if (localStorage.getItem("authToken")) {
      navigate("/home");
  }
}

useEffect(() => {
  window.addEventListener("storage", checkAuth);
  return () => {
    window.removeEventListener("storage", checkAuth)
  }
}, []);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM