Home > database >  After login, it always redirect me to login page when I go to dashboard page and then reload the das
After login, it always redirect me to login page when I go to dashboard page and then reload the das

Time:02-17

I have used react-router-dom v6 and google firebase authentication. In react-router dom v5, I have added, it is working well, and in v6 I have changed some from the v6 documentation. I couldn't find the wrong.

useFirebase.js :

import { useEffect, useState } from "react";
import initializeFirebase from "./../Pages/Authentication/firebase/firebase.init";
import {
  getAuth,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  onAuthStateChanged,
  updateProfile,
  signOut,
} from "firebase/auth";
initializeFirebase();

const useFirebase = () => {
  const [user, setUser] = useState({});
  const [userData, setUserData] = useState({});
  const [isLoading, setIsLoading] = useState(false);
  const [optStatus, setOtpStatus] = useState("");
  const [registrationStatus, setRegistrationStatus] = useState(false);
  const [authError, setAuthError] = useState("");
  const [admin, setAdmin] = useState(false);
  const [assitantAdmin, setAssistantAdmin] = useState(false);
  const [applicant, setApplicant] = useState(false);
  const auth = getAuth();

  const defaultOTP = "123456";

  const registerUser = (
    email,
    password,
    name,
    phone,
    dateOfBirth,
    institutionId,
    employeeId,
    employeeType,
    institution,
    requestedRole,
    role
  ) => {
    setIsLoading(true);
    createUserWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        setAuthError("");
        const newUser = {
          email,
          displayName: name,
          photoURL: "",
          phoneNumber: phone,
          dateOfBirth,
          institutionId,
          employeeId,
          employeeType,
          institution,
          requestedRole,
          role,
        };
        setUser(newUser);

        //save user to the database
        saveUser(
          email,
          name,
          "",
          phone,
          dateOfBirth,
          institutionId,
          employeeId,
          employeeType,
          institution,
          requestedRole,
          role,
          "POST"
        );
        // send name to firebase after register with email and password
        updateProfile(auth.currentUser, {
          displayName: name,
        })
          .then(() => {})
          .catch((error) => {});
      })
      .catch((error) => {
        setAuthError(error.message);
      })
      .finally(() => setIsLoading(false));
  };

  // sign is with email and password
  const loginUserWithEmailPassword = (email, password, navigate) => {
    setIsLoading(true);
    signInWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        // const destination = location?.state?.from || "/";
        navigate("/home");
        setAuthError("");
      })
      .catch((error) => {
        setAuthError(error.message);
      })
      .finally(() => setIsLoading(false));
  };

  //observe user state
  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      if (user) {
        setUser(user);
      } else {
        setUser({});
        setUserData({});
      }
    });
    setIsLoading(false);
    return () => unsubscribe;
  }, []);

  //check and set admin
  useEffect(() => {
    fetch(`http://localhost:5000/users/${user.email}`)
      .then((res) => res.json())
      .then((data) => {
        setUserData(data.userData);
        setAdmin(data.admin);
        setAssistantAdmin(data.assitantAdmin);
        setApplicant(data.applicant);
      });
  }, [user.email]);

  // save users into database
  const saveUser = (
    email,
    displayName,
    photoURL,
    phoneNumber,
    dateOfBirth,
    institutionId,
    employeeId,
    employeeType,
    institution,
    requestedRole,
    role,
    method
  ) => {
    const user = {
      email,
      displayName,
      photoURL,
      phoneNumber,
      dateOfBirth,
      institutionId,
      employeeId,
      employeeType,
      institution,
      requestedRole,
      role,
    };
    if (method === "POST" || method === "PUT" || method === "DELETE") {
      fetch("http://localhost:5000/users", {
        method: method,
        headers: {
          "content-type": "application/json",
        },
        body: JSON.stringify(user),
      }).then();
    } else {
      fetch("http://localhost:5000/users", {
        method: method,
        headers: {
          "content-type": "application/json",
        },
      }).then();
    }
  };

  // log out user
  const logOut = () => {
    signOut(auth)
      .then(() => {
        // Sign-out successful.
      })
      .catch((error) => {})
      .finally(() => setIsLoading(false));
  };

  return {
    user,
    userData,
    admin,
    assitantAdmin,
    applicant,
    authError,
    isLoading,
    registrationStatus,
    defaultOTP,
    optStatus,
    setIsLoading,
    setOtpStatus,
    setRegistrationStatus,
    registerUser,
    loginUserWithEmailPassword,
    logOut,
  };
};
export default useFirebase;

App.js :

<AuthProvider>
    <Router>
       <Routes>
        <Route
          path='/dashboard/*'
          element={
            <PrivateRoute>
              <Dashboard />
            </PrivateRoute>
          }
        />
        <Route path='/login' element={<Login />} />
      </Routes>
    </Router>
  </AuthProvider>

PrivateRoute.js :

// this is private route
const PrivateRoute = ({ children }) => {
  let { user, isLoading } = useAuth();
  let location = useLocation();

  if (isLoading) {
    return <Spinner className='my-5' animation='border' variant='success' />;
  }
  if (user?.email) {
    return children;
  }
  return (
    <Navigate
      to={{
        pathname: "/login",
        state: { from: location },
      }}
    />
  );
};

export default PrivateRoute;

Login.js :

const alertStyle = {
  justifyContent: "center",
  marginLeft: "auto",
  marginRight: "auto",
};

user log in page:

const Login = () => {
  const [loginData, setLoginData] = useState({});
  const { user, loginUserWithEmailPassword, isLoading, authError } = useAuth();

  let location = useLocation();
  const navigate = useNavigate();

  const handleOnBlur = (e) => {
    const field = e.target.name;
    const value = e.target.value;
    const newLoginData = { ...loginData };
    newLoginData[field] = value;
    setLoginData(newLoginData);
  };
  //handle log in using email password
  const handleLogin = (e) => {
    loginUserWithEmailPassword(
      loginData.email,
      loginData.password,
      location,
      navigate
    );
    e.preventDefault();
  };

  return (
    <div>
      <Navigation />
      <div className='container'>
        <p className='mt-5'>
          <b>Log In for Admin</b>
        </p>
        <div className='mb-5'>
          {/* register form */}
          {!isLoading && (
            <form onSubmit={handleLogin}>
              <br />
              <div className='mb-3'>
                <input
                  type='email'
                  name='email'
                  onBlur={handleOnBlur}
                  style={{ width: "300px" }}
                  className='form-control mx-auto'
                  id='exampleFormControlInput1'
                  placeholder='Your Email'
                />
              </div>
              <div className='mb-3'>
                <input
                  type='password'
                  name='password'
                  onBlur={handleOnBlur}
                  style={{ width: "300px" }}
                  className='form-control mx-auto'
                  id='exampleFormControlInput1'
                  placeholder='Your Password'
                />
              </div>
              <button
                type='submit'
                style={{ width: "300px" }}
                className='btn btn-success mx-auto'
              >
                Login
              </button>
            </form>
          )}
          {/* Spinner */}
          {isLoading && (
            <Spinner className='my-5' animation='border' variant='success' />
          )}

          <p className='mt-3'>
            <b>New User? </b>
            <NavLink to='/signup' className='text-decoration-none'>
              <b>Register & Submit Application</b>
            </NavLink>
          </p>
        </div>
        {/* success and error alert */}
        {user?.email && (
          <Alert severity='success' style={alertStyle}>
            Account created successfully!
          </Alert>
        )}
        {authError && (
          <Alert severity='error' style={alertStyle}>
            {authError}
          </Alert>
        )}
     </div>
    </div>
  );
};

export default Login;

CodePudding user response:

Issue

I think the issue is that the initial isLoading state is false:

const [isLoading, setIsLoading] = useState(false);

and the initial user state is truthy:

const [user, setUser] = useState({});

When reloading the page after authenticating the loading state is never triggered:

//observe user state
useEffect(() => {
  const unsubscribe = onAuthStateChanged(auth, (user) => {
    if (user) {
      setUser(user);
    } else {
      setUser({});
      setUserData({});
    }
  });
  setIsLoading(false);
  return () => unsubscribe;
}, []);

In PrivateRoute the isLoading check fails because it is false, and so the user.email property is checked. Because the user object isn't populated yet the redirect occurs.

const PrivateRoute = ({ children }) => {
  let { user, isLoading } = useAuth();
  let location = useLocation();

  if (isLoading) { // <-- (1) false, so no spinner
    return <Spinner className='my-5' animation='border' variant='success' />;
  }
  if (user?.email) { // <-- (2) undefined, so no children
    return children;
  }
  return (
    <Navigate // <-- (3) navigate 
      to={{
        pathname: "/login",
        state: { from: location },
      }}
    />
  );
};

Solution

I believe the solution is trivial, start with an initially loading private route component.

const [isLoading, setIsLoading] = useState(true);

The allows the mounting auth check to work for your UI instead of against it.

//observe user state
useEffect(() => {
  const unsubscribe = onAuthStateChanged(auth, (user) => {
    if (user) {
      setUser(user);
    } else {
      setUser({});
      setUserData({});
    }
  });
  setIsLoading(false); // <-- (2) done initializing, isLoading false
  return () => unsubscribe;
}, []); // <-- (1) mounting useEffect, start isLoading true

So now the PrivateRoute component will receive an isLoading true on the initial render cycle.

  • Related