I need to secure some routes of my react application, and for this I have created a private route component like this:
import React from 'react';
import {useAuth} from "../../context/AuthContextProvider";
import {Navigate} from "react-router-dom";
import {useLocation} from "react-router";
const PrivateRoute = ({children}) => {
const {user, isAuthenticating, isAuthenticated} = useAuth();
const location = useLocation();
console.log(user, isAuthenticating, isAuthenticated);
return isAuthenticated ? children : <Navigate to="/sign-in" state={{from: location}} replace/>;
}
export default PrivateRoute;
In the authentication context provider I check if the user is logged in or not like this:
import React, {createContext, useContext, useEffect, useState} from "react";
import {useMeMutation} from "../data/user";
const AuthContext = createContext();
export const useAuth = () => useContext(AuthContext);
const AuthContextProvider = ({children}) => {
const {mutate: me} = useMeMutation();
const [auth, setAuth] = useState({
user: null,
isAuthenticating: null,
isAuthenticated: null
});
useEffect(() => {
checkAuthentication();
}, []);
const revalidate = () => {
return me({}, {
onSuccess: ({data}) => {
console.log(data);
setAuth({
user: data,
isAuthenticating: false,
isAuthenticated: true
});
},
one rror: (error) => {
if ((error.response && error.response.status === 401) ||
(error.response && error.response.status === 403)) {
setAuth({
user: null,
isAuthenticating: false,
isAuthenticated: false
});
}
},
});
};
const checkAuthentication = () => {
if (auth.isAuthenticated === null) {
revalidate();
}
};
return (
<AuthContext.Provider value={{
...auth,
revalidate
}}>{children}</AuthContext.Provider>
);
};
export default AuthContextProvider;
The problem with this code is the login component is shown before the user is checked on the api side. My routes are like this:
<Routes>
<Route element={<PublicLayout/>}>
{publicRoutes.map(({component: Component, path, exact}) => (
<Route
path={`/${path}`}
key={path}
exact={exact}
element={<Component/>}
/>
))}
</Route>
<Route element={<PrivateRoute><PrivateLayout/></PrivateRoute>}>
{privateRoutes.map(({component: Component, path, exact}) => (
<Route
path={`/${path}`}
key={path}
exact={exact}
element={<Component/>}
/>
))}
</Route>
</Routes>
CodePudding user response:
In the private route component, I need to stop the render for thoses two states:
When the user come the first time to the login page, the value of isAuthenticated should be null and like that the private route is rendered when this value is false,
When the user click in the login button, I need to set the isAuthenticating to true, in this case the state of the user is not yet known so i need also to block the render of private route
const PrivateRoute = ({children}) => { const {user, isAuthenticating, isAuthenticated} = useAuth(); const location = useLocation(); if (isAuthenticated === null || isAuthenticating === true) { return null; } return isAuthenticated ? children : <Navigate to="/sign-in" state={{from: location}} replace/>; }
And in the context provider i need to specify the isAuthenticating in the begin of call to the api like this:
const revalidate = () => {
setAuth({
user: null,
isAuthenticating: true,
isAuthenticated: false
});
return me({}, {}
...