Reputation: 31
I created a protected route that is meant to call a function getAuth()
that:
The purpose of this protected route is authenticate the user by resetting the global context after each page, and to then use that global context to redirect them to the proper page if they fail to authenticate.
If I didn't use the getAuth()
function, and just relied on the global context, when a user would refresh their page, the global context would be lost.
My code for the UserProtected route is the following:
//UserProtecterRoute.js
import { useMemo, useRef, useState } from "react";
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
export default function UserProtectedRoute() {
const getAuth = useAuth().GetAuth;
const useAuthState = useAuth().authState;
var authorized = useRef(false);
useMemo(() => {
if (localStorage.getItem("token")) {
getAuth();
if (useAuthState.username) {
authorized.current = true;
}
}
}, []);
if (authorized.current) {
return <Outlet />;
} else {
return <Navigate to="/Login"></Navigate>;
}
}
The code for the global authorization context is the following:
//AuthContext.js
import React, { useContext, useState } from "react";
import * as ReactDOM from "react-dom";
import axios from "axios";
const AuthContext = React.createContext();
export function useAuth() {
// returns a function which once run returns an object with the values you
// placed in it. In this case it returns {authState,setAuthState,GetAuth}
// you can call this object.prop to use the prop
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [authState, setAuthState] = useState({
id: 0,
username: "",
role: "user",
});
async function GetAuth() {
if (localStorage.getItem("token")) {
await axios
.get("http://localhost:3001/Auth", {
headers: {
accessToken: localStorage.getItem("token"),
},
})
.then((res) => {
setAuthState((prev) => ({
...prev,
id: res.data.id,
username: res.data.user,
role: res.data.role,
}));
})
.catch((error) => {
console.log(error);
});
}
}
return (
<AuthContext.Provider value={{ authState, setAuthState, GetAuth }}>
{children}
</AuthContext.Provider>
);
}
When I run my code to debug, it follows this order:
if(useAuthState.username)
statement in the useMemo is run, but useAuthState.username is undefinedI believe that the issue has something to do with promises, I tried wrapping the if(localStorage.getItem('token'))
in an async function and putting await in from of getAuth, but no luck there either. I'm also suspecting there might be something to do with when setState gets implemented, but my experience in React and Promises is such that I simply can't figure it out by myself.
If someone could guide me to what I'm doing wrong, I would greatly appreciate it.
Upvotes: 2
Views: 368
Reputation: 164766
I would do the following instead...
AuthProvider
component on mount. That way you don't have to delegate it to your other components.ref
In your context provider...
// This could be in a separate, testable module
const fetchAuth = async (accessToken) => {
const {
data: { id, user, role },
} = await axios.get("http://localhost:3001/Auth", {
headers: { accessToken },
});
return { id, username: user, role };
};
export function AuthProvider({ children }) {
const [authState, setAuthState] = useState({
id: 0,
username: "",
role: "user",
});
// Loading state
const [isLoading, setIsLoading] = useState(false);
// Helper memo
const isAuthenticated = useMemo(() => !!authState.username, [authState]);
const getAuth = async () => {
setIsLoading(true);
const token = localStorage.getItem("token");
try {
if (token) {
setAuthState(await fetchAuth(token));
}
} catch (err) {
console.error(err.toJSON());
} finally {
setIsLoading(false);
}
};
// Call getAuth on mount
useEffect(() => {
getAuth();
}, []);
return (
<AuthContext.Provider
value={{ authState, getAuth, isAuthenticated, isLoading }}
>
{children}
</AuthContext.Provider>
);
}
and in your component...
//UserProtecterRoute.js
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
export default function UserProtectedRoute() {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) {
// just an example
return <p>Loading...</p>;
}
if (isAuthenticated) {
return <Outlet />;
}
return <Navigate to="/Login"></Navigate>;
}
Upvotes: 2