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} />;
}
You can replace:
const [userToken, setUserToken] = useState(null);
With
const [userToken, setUserToken] = useState(
()=>JSON.parse(localStorage.getItem('userToken')
);
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)useEffect
(but the render always finishes first)