I have created this Auth
Component and it works fine. Except that, It does not redirect if the unauthenticated user tries to visit /dashboard
.
The backend upon receiving /api/me
request, knows the user by having the cookie. So I have (Cookie-Session) Authentication technique.
export const UserContext = createContext();
const Auth = ({ children }) => {
const [user, setUser] = useState(null);
const [gotUser, setGotUser] = useState(false);
const navigate = useNavigate();
const getUser = async () => {
const res = await fetch('/api/me');
const data = await res.json();
setUser(data);
if (user) {
setGotUser(true);
}
};
useEffect(() => {
if (!gotUser) {
getUser();
}
}, [user, gotUser, navigate]);
if (!user) {
navigate('/login');
}
console.log(user);
return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};
So the main issue is that no redirection done. Also, The user
passed to the context is not updated properly. Maybe because I am confused about what to use in useEffect
.
Any help is appreciated.
There are a couple issues:
user
state is confirmed.navigate
function is called as an unintentional side-effect directly in the body of the component. Either move the navigate
call into a useEffect
hook or render the Navigate
component to issue the imperative navigation action.Use an undefined
initial user
state and explicitly check that prior to issuing navigation action or rendering the UserContext.Provider
component.
const Auth = ({ children }) => {
const [user, setUser] = useState(); // <-- initially undefined
const navigate = useNavigate();
const getUser = async () => {
try {
const res = await fetch('/api/me');
const data = await res.json();
setUser(data); // <-- ensure defined, i.e. user object or null value
} catch (error) {
// handler error, set error state, etc...
setUser(null); // <-- set to null for no user
}
};
useEffect(() => {
if (user === undefined) {
getUser();
}
}, [user]);
if (user === undefined) {
return null; // <-- or loading indicator, spinner, etc
}
// No either redirect to log user in or render context provider and app
return user
? <Navigate to="/login" replace />
: <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};
useEffect
runs after your JSX is rendered, so as your code is made, on a page refresh this if (!user)
that calls navigate('/login')
will always pass, as before the useEffect
does its work, user
is null
, that inital value you gave to useState
. Yet it's not redirecting because navigate
does not work inside JSX, it should be replaced with Navigate
the component.
Also, in getUser
, you have this if (user)
juste after setUser(data)
, that wouldn't work well as user
won't get updated immediately, as updating a state
is an asynchronous task which takes effect after a re-redner .
To fix your problems you can add a checking
state, return some loader while the user is being verified. Also you can optimise a little bit your code overall, like getting ride of that gotUser
state:
export const UserContext = createContext();
const Auth = ({ children }) => {
const [user, setUser] = useState(null);
const [checking, setChecking] = useState(true);
const getUser = async () => {
try {
const res = await fetch("/api/me");
const data = await res.json();
setUser(data);
} catch (error) {
setUser(null);
} finally {
setChecking(false);
}
};
useEffect(() => {
if (!user) {
getUser();
}
}, [user]);
if (checking) {
return <p>Checking...</p>;
}
if (!user) {
return <Navigate to="/login" replace />
}
return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};
export default Auth;