1

I am developing a React application and I have set some of my route information to be private and some to be private based on role. However, when I log in, the system works fine, but when I refresh the page, that is, when I render, it throws it back to the login page and the information stays in localStorage. I have a checkLogin method, but either I am using it incorrectly or it does not work.

How can I solve this error? When I load the page for the first time, the usertoken in useAuth appears, but when the page is rendered, it returns null and redirects to the login page. How can I solve it?

const PrivateRoutes = () => {
    const {userToken} = useAuth();
  
  
     return userToken ? <Outlet /> : <Navigate to="/login" replace />;
    }


       export default PrivateRoutes;
     function AppRoutes() {
    return (
    <Routes>
      <Route element={<Login />} path="/login" />
      <Route element={<Register />} path="/register" />
      <Route element={<NoMatch />} path="*" />

      <Route element={<PrivateRoutes />}>
        <Route element={<MainLayout />} path='/'>
          <Route element={<MyProfile />} path='/myprofile' />
          <Route element={<ChangePassword />} path='/changepassword' />
          <Route element={<ProtectedRoute roles={[ROLES.CLIENT]} component={ClientUserList} />} path="/client/user/list" />
          <Route element={<ProtectedRoute roles={[ROLES.CLIENT]} component={ClientUserCreate} />} path="/client/user/create" />
          <Route element={<ProtectedRoute roles={[ROLES.CLIENT]} component={ClientUserEdit} />} path="/client/user/edit/:id" />
          <Route element={<ProtectedRoute roles={[ROLES.CLIENT]} component={ClientUserShow} />} path="/client/user/show/:id" />
          <Route element={<ProtectedRoute roles={[ROLES.CLIENT]} component={ManageInfo} />} path="/manage" />
          <Route element={<ProtectedRoute roles={[ROLES.CLIENT]} component={ManageEdit} />} path="/manage/edit" />
          <Route element={<ProtectedRoute roles={[ROLES.CLIENT]} component={CategoryList} />} path="/category/list" />
    </Routes>
  )
}

Context Page:

const AuthContext = createContext({});

const AuthProvider = ({ children }) => {
    const [userToken, setUserToken] = useState(null);
    const [userInfo, setUserInfo] = useState(null);
    const navigate = useNavigate();


    const checkLogin = async () => {
               console.log("checking login info");
               const tokenInfo = JSON.parse(localStorage.getItem("userToken"));
                if (tokenInfo != null) {
                setUserToken(tokenInfo);
                var userData = await accountService.getInfo();
             if (userData.result > 0) {
                localStorage.setItem("userInfo", JSON.stringify(userData));
                  setUserInfo(userData);
                  } else {
                   localStorage.removeItem("userToken");
                 localStorage.removeItem("userInfo");
               setUserToken(null);
               setUserInfo(null);
                 navigate("/login");
                }
             } else {
                 setUserToken(null);
             }
          };

       const doLogin = (tokenData) => {
        console.log("token",tokenData);
       if (tokenData.result > 0) {
       localStorage.setItem("userToken", JSON.stringify(tokenData));
         setUserToken(tokenData);
         navigate("/");
         getInfo();
          }
};

Protected Page:

function ProtectedRoute({ component: Component, roles, ...rest }) {
    const { userToken } = useAuth();
    const token= userToken.data.token;
     const userRole = getRole(token);

     if (!userToken) {
       return <Navigate to="/login" replace />;
     }

     if (roles && roles.indexOf(userRole) === -1) {
       return <Navigate to="/myprofile" replace />;
      }

   return <Component {...rest} />;
}
mousetail
  • 7,009
  • 4
  • 25
  • 45
Goktug
  • 37
  • 6
  • `const [userToken, setUserToken] = useState(()=>JSON.parse(localStorage.getItem('userToken'));` – mousetail May 08 '23 at 12:35
  • @mousetail My problem is solved, why did we do such a thing? I mean why isn't it updating itself? – Goktug May 08 '23 at 12:51
  • No, this actually prevents it from updating by making the initial state the correct state. If, even briefly, the user does not exist (because it's still loading) it will redirect to the login page. This will prevent the user from ever being `null` and causing the redirect – mousetail May 08 '23 at 12:53
  • I'll write a answer with a bit more detail – mousetail May 08 '23 at 12:56
  • @mousetail Could you give a little more detail, I don't understand very well. – Goktug May 08 '23 at 12:58

1 Answers1

0

You can replace:

const [userToken, setUserToken] = useState(null);

With

const [userToken, setUserToken] = useState(
    ()=>JSON.parse(localStorage.getItem('userToken')
);

Why?

Imagine code like this:

const Component = ({redirect}) => {
    const [isLoggedIn, setLoggedIn] = useState(false);
    
    useEffect(()=>setLoggedIn(true), []);

    if (!loggedIn) {
        redirect(some_other_page);
    }

    return <div>log in state: {''+isLogged</div>
}

This is basically the code you have. You have a lot of added complexity, like the user info is more complex than a boolean and more nested components, but this is basically the issue.

Notice what happens here initially:

  • isLoggedIn is set to False initially (null in your case)
  • You queue it to be set to true next render in useEffect (but the render always finishes first)
  • If you haven't logged in, your page is changed now.
  • Next render, both the page changes as the logged in state
  • However, logged in state no longer matters because the page has changed
mousetail
  • 7,009
  • 4
  • 25
  • 45