1

React Beginner here. I'm building a login form in React using jwt, axios and useContext. After being authorized from the backend, I store the data in the global context using AuthProvider and redirect to home page. the home page first checks for authorization and navigates to login on unauthorized access. However even after updating the auth (useState) on login, I still get a false value on the first click and get sent back to login even if authed. I've tried useEffects everywhere but to no avail. Code below

AuthProvider.jsx

import React, { useState } from "react";
import { createContext } from "react";

const AuthContext = createContext();
export default AuthContext

export const AuthProvider = ({ children }) => {
    const [user, setUser] = useState(null);
    const [authed, setAuthed] = useState(false);

    function login(user) {
        setUser(user);
        setAuthed(true);
    }
    
    return (
        <AuthContext.Provider value={{login, user, authed}}>
            {children}
        </AuthContext.Provider>
    )
}

ProtectedRoute.jsx to /home

import React, { useContext, useEffect } from "react";
import AuthContext from "../../context/AuthProvide";

const ProtectedRoute = ({children}) => {

    const {login, user, authed} = useContext(AuthContext);
    useEffect(() => {
        alert("HELLO")
        alert(authed)
        if (!authed) {
            return window.location.href = "/login";
        } else {
            return children
        }
    }, [authed, user, login]);
}
export default ProtectedRoute;

Top part of Login.jsx

import React, { useState, useEffect, useRef, useContext } from "react";
import "./login.css";
import AuthContext from "../../context/AuthProvide";

import { axios } from "../../context/axios";
const LOGIN = "/login";

const Login = () => {
    const {login, user, authed} = useContext(AuthContext);
    const [userEmail, setUserEmail] = useState("");

    const [userPassword, setUserPassword] = useState("");
    const [error, setError] = useState("");

    const errorRef = useRef();
    useEffect(() => {
        
    }, [authed, user, login]);

    useEffect(() => {
        setError("");
    }, [userEmail, userPassword]);


    function handleUserEmail(event) {
        setUserEmail(event.target.value);
    }

    function handleUserPassword(event) {
        setUserPassword(event.target.value);
    }

    function handleSubmit(event) {
        event.preventDefault();
        axios.post(LOGIN, {
            email: userEmail,
            password: userPassword
        }).then(response => {
            if (response.data.error) {
                setError(response.data.error);
            } else {
                // this is supposed to be the one to set the user and auth to true
                login(response.data.token)
                alert(authed)
                    window.location.href = "/";
            }
        }).catch(error => {
            if (!error?.response) {
                setError("NO SERVER RESPONSE");
            } else if (error.response?.status === 400) {
                setError("MISSING USER NAME OR PASSWORD");
            } else if (error.response?.status === 401) {
                setError("UNAUTHORIZED ACCESS");
            } else {
                setError("UNKNOWN ERROR");
            }
            errorRef.current.focus();
        })
    }

    function resetForm() {
        setUserEmail("");
        setUserPassword("");
    }
  return (
  // the form is here
  )
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
Nugget
  • 81
  • 1
  • 6
  • 23

1 Answers1

1

Issues

The main issue I see with the code is the use of window.location.href. When this is used it reloads the page. This remounts the entire app and any React state will be lost/reset unless it is being persisted to localStorage and used to initialize app state.

It is more common to use the navigation tools from react-router-dom (I'm assuming this is the package being used, but the principle translates) to issue the imperative and declarative navigation actions.

Suggestions

The protected route component should either redirect to the login path or render the children prop if user is authorized. It passes the current location being accessed along in route state so user can be redirected back after successful authentication.

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

const ProtectedRoute = ({ children }) => {
  const location = useLocation();
  const { authed } = useContext(AuthContext);
  if (!authed) {
    return <Navigate to="/login" replace state={{ from: location }} />;
  } else {
    return children;
  }
};

The Login component should use the useNavigate hook to use the navigate function to redirect user to protected route once authenticated.

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

const Login = () => {
  const { state } = useLocation();
  const navigate = useNavigate();
  const { login, user, authed } = useContext(AuthContext);
  ...

  function handleSubmit(event) {
    event.preventDefault();
    axios.post(
      LOGIN,
      {
        email: userEmail,
        password: userPassword
      }
    )
      .then(response => {
        if (response.data.error) {
          setError(response.data.error);
        } else {
          login(response.data.token)
          navigate(state?.from?.pathname ?? "/", { replace: true });
        }
      })
      .catch(error => {
        if (!error?.response) {
          setError("NO SERVER RESPONSE");
        } else if (error.response?.status === 400) {
          setError("MISSING USER NAME OR PASSWORD");
        } else if (error.response?.status === 401) {
          setError("UNAUTHORIZED ACCESS");
        } else {
          setError("UNKNOWN ERROR");
        }
        errorRef.current.focus();
      });
  }

  ...

  return (
    // the form is here
  );
}

Wrap the routes you want to protect with the ProtectedRoute component.

<AuthProvider>
  <Routes>
    ...
    <Route path="/login" element={<Login />} />
    <Route
      path="/test"
      element={
        <ProtectedRoute>
          <h1>Protected Test Route</h1>
        </ProtectedRoute>
      }
    />
  </Routes>
</AuthProvider>

Edit react-form-submit-must-be-clicked-twice-to-set-context-globally-using-usecontex

Drew Reese
  • 165,259
  • 14
  • 153
  • 181