Search code examples
javascriptreactjsreact-routerreact-router-dom

How can I make redirect on page after login correctly?


I am trying to redirect user after successful login. I did it this way but it does not work. How can I fix this issue or how can I do it correctly?

main.jsx

enter image description here

Login.jsx

imports ...

function Login() {
    const [login, setLogin] = useState('');
    const [password, setPassword] = useState('');
    const [loginClass, setLoginClass] = useState('');
    const [passwordClass, setPasswordClass] = useState('');

    const [isLoggedIn, setIsLoggedIn] = useState(false);

    const submit = async (evt) => {
        evt.preventDefault();
        console.log(login, password);
        if (login.length === 0 || password.length === 0) {
            console.log('stop')
            if (!login) { 
                setLoginClass('is-invalid'); 
            }

            if (!password) { 
                setPasswordClass('is-invalid');
            }
        } else {
            console.log('req')
            await axios
                .post('http://localhost:5000/test/auth/login', {
                    login: login,
                    password: password
                })
                .then((response) => {
                    console.log(response);
                    const { data } = response;
                    if (data.status == 200) {
                        console.log('entered successfully');
                        setIsLoggedIn(true);
                    } else {
                        console.log('could not enter');
                    }
                })
                .catch((error) => {
                    console.log(error);
                });

            setPassword('');
            setLogin('');
            setLoginClass('');
            setPasswordClass('');
        }
        if (isLoggedIn) {
            return <Navigate to="/main" replace={true}/>
        }
        return
    };

    return (
        <div className="container col-xl-10 col-xxl-8 px-4 py-5">
            ...
                            <input
                                type="text"
                                className={`form-control ${loginClass}`}
                                id="login"
                                placeholder="Login"
                                value={login}
                                onChange={(evt) => { setLogin(evt.target.value) }}
                            />
                            ...
                            <input
                                type="password"
                                className={`form-control ${passwordClass}`}
                                id="password"
                                placeholder="Password"
                                value={password}
                                onChange={(evt) => { setPassword(evt.target.value) }}
                            />
                        <button className="w-100 btn btn-lg btn-primary" onClick={submit}>Enter</button>
                    </form>
                </div>
            </div>
        </div>
    );
}

export default Login;

Solution

  • Issues

    • Attempting to return JSX from an asynchronous callback function.

      The code is attempting to issue a redirect from within the submit handler. You can't return JSX from a callback and expect it to be rendered to the DOM and have any effect. Navigate would need to be returned from the Login as part of the regular JSX it renders.

    • Attempting to access a stale closure over state.

      submit enqueues some state updates and the code incorrectly attempts to access the isLoggedIn state to effect a redirect action.

    • It is generally considered a Javascript anti-pattern to mix async/await with Promise chains. Pick one pattern or the other.

    const submit = async (evt) => {
      evt.preventDefault();
      console.log(login, password);
      if (login.length === 0 || password.length === 0) {
        console.log('stop')
        if (!login) { 
          setLoginClass('is-invalid'); 
        }
    
        if (!password) { 
          setPasswordClass('is-invalid');
        }
      } else {
        console.log('req')
        await axios
          .post('http://localhost:5000/test/auth/login', {
            login: login,
            password: password
          })
          .then((response) => {
            console.log(response);
            const { data } = response;
            if (data.status == 200) {
              console.log('entered successfully');
              setIsLoggedIn(true);
            } else {
              console.log('could not enter');
            }
          })
          .catch((error) => {
            console.log(error);
          });
    
        setPassword('');
        setLogin('');
        setLoginClass('');
        setPasswordClass('');
      }
    
      if (isLoggedIn) { // <-- won't have value from any state enqueued updates above
        return <Navigate to="/main" replace={true} />; // <-- can't do this
      }
      return
    };
    

    Solutions

    1. Enqueue the isLoggedIn state update as you are and return the Navigate component with the regular JSX to issue a declarative redirect.

      function Login() {
        const [login, setLogin] = useState('');
        const [password, setPassword] = useState('');
        const [loginClass, setLoginClass] = useState('');
        const [passwordClass, setPasswordClass] = useState('');
      
        const [isLoggedIn, setIsLoggedIn] = useState(false);
      
        const submit = async (evt) => {
          evt.preventDefault();
      
          if (!login.length || !password.length) {
            if (!login) { 
              setLoginClass('is-invalid'); 
            }
      
            if (!password) { 
              setPasswordClass('is-invalid');
            }
          } else {
            try {
              const { data } = await axios.post(
                'http://localhost:5000/test/auth/login',
                { login, password }
              )
      
              if (data.status == 200) {
                setIsLoggedIn(true);
              } else {
                console.log('could not enter');
              }
            } catch(error) {
              console.log(error);
            };
      
            setPassword('');
            setLogin('');
            setLoginClass('');
            setPasswordClass('');
          }
        };
      
        // Return JSX from function component
        if (isLoggedIn) {
          return <Navigate to="/main" replace />; // <-- declarative redirect
        }
      
        return (
          ...
        );
      }
      
    2. Use the useNavigate hook and issue an imperative redirect from the submit handler.

      import { useNavigate } from 'react-router-dom';
      ...
      
      function Login() {
        const navigate = useNavigate();
      
        const [login, setLogin] = useState('');
        const [password, setPassword] = useState('');
        const [loginClass, setLoginClass] = useState('');
        const [passwordClass, setPasswordClass] = useState('');
      
        const submit = async (evt) => {
          evt.preventDefault();
      
          if (!login.length || !password.length) {
            if (!login) { 
              setLoginClass('is-invalid'); 
            }
      
            if (!password) { 
              setPasswordClass('is-invalid');
            }
          } else {
            try {
              const { data } = await axios.post(
                'http://localhost:5000/test/auth/login',
                { login, password }
              )
      
              if (data.status == 200) {
                navigate("/main", { replace: true }); // <-- imperative redirect
              } else {
                console.log('could not enter');
              }
            } catch(error) {
              console.log(error);
            };
      
            setPassword('');
            setLogin('');
            setLoginClass('');
            setPasswordClass('');
          }
        };
      
        return (
          ...
        );
      }