Whenever I access the "/projects"
route via url, it redirects to "/"
, but I can access it via a button for example.
I am 100% sure it's something wrong with the AuthContext
, because if I just put true
on the condition
arg on PrivateRoute
component, it works, but if the condition come from the AuthContext
, it's buggy.
This is my Router
component:
// private route, if condition is true, it render the children component,
// else, redirect to 'redirectRoute'
function PrivateRoute({ condition, redirectRoute = '/login' }) {
const auth = useAuth();
if(auth.loading) return null;
return (
condition
? <Outlet></Outlet>
: <Navigate to={redirectRoute}></Navigate>
)
}
function Router() {
// this useAuth() is basically a useContext(AuthContext)
const auth = useAuth();
return (
<BrowserRouter>
<Routes>
<Route
exact
path="/"
element={<PrivateRoute condition={auth.isLogged}></PrivateRoute>}
>
<Route exact path="/" element={<Home></Home>}></Route>
</Route>
<Route
exact
path="/projects"
element={<PrivateRoute condition={auth.isLogged}></PrivateRoute>}
>
<Route
exact
path="/projects"
element={<Projetos></Projetos>}
></Route>
</Route>
</Routes>
</BrowserRouter>
);
}
And this is my AuthContext.js:
export const AuthContext = createContext({});
export function AuthProvider({children}) {
const [isLogged, setIsLogged] = useState(false);
const [loading, setLoading] = useState(false);
useEffect(() => {
// this function send the jwt token to backend to verify
// if its valid, if so, it do a setIsLogged(true)
async function checkToken() {
const token = JSON.parse(localStorage.getItem('costs_token'));
if (!token) return false;
const headers = {
'Content-Type': 'application/json',
authorization: 'Bearer '+token
}
const response = await fetch('/auth/validatetoken', {
method: 'GET',
headers
});
if (response.status === 200) {
setIsLogged(true);
return true;
}
localStorage.removeItem('costs_token');
return false;
}
setLoading(true);
checkToken().then(() => setLoading(false));
}, []);
return (
<AuthContext.Provider value={{isLogged, loading}}>
{children}
</AuthContext.Provider>
);
}
I tried using a boolean value that do not come from AuthContext
, it worked, but if the boolean come from the AuthContext
, I can't access it directly via url, it redirects to "/"
.
Start the app with an initially true loading
state value so the PrivateRoute
"waits" for the initial auth check to complete. When loading
is initially false the PrivateRoute
component fails the if-loading condition and conditionally renders the protected content or the redirect based on the passed condition
prop. Since auth.isLogged
is also initially false the Navigate
component is rendered.
export function AuthProvider({ children }) {
const [isLogged, setIsLogged] = useState(false);
const [loading, setLoading] = useState(true); // <-- initially true
useEffect(() => {
// this function send the jwt token to backend to verify if its valid,
// if so, it do a setIsLogged(true)
async function checkToken() {
const token = JSON.parse(localStorage.getItem('costs_token'));
if (!token) return false;
const headers = {
'Content-Type': 'application/json',
authorization: 'Bearer '+token
}
const response = await fetch('/auth/validatetoken', {
method: 'GET',
headers
});
if (response.status === 200) {
setIsLogged(true);
return true;
}
localStorage.removeItem('costs_token');
return false;
}
setLoading(true);
checkToken().then(() => setLoading(false));
}, []);
return (
<AuthContext.Provider value={{isLogged, loading}}>
{children}
</AuthContext.Provider>
);
}
function PrivateRoute({ condition, redirectRoute = '/login' }) {
const auth = useAuth();
// Wait until auth status confirmed/loaded
if (auth.loading) return null;
return condition
? <Outlet />
: <Navigate to={redirectRoute} replace />;
}