Search code examples
javascriptreactjsreact-routerreact-router-dom

How can I check userToken in React?


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} />;
}

Solution

  • 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