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.
CodePudding user response:
Issues
There are a couple issues:
- The "unauthenticated" state matches the "I don't know yet" state (i.e. the initial state value) and the component is redirecting too early. It should wait until the
user
state is confirmed. - The
navigate
function is called as an unintentional side-effect directly in the body of the component. Either move thenavigate
call into auseEffect
hook or render theNavigate
component to issue the imperative navigation action.
Solution
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>;
};
CodePudding user response:
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 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 getupdated 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) {
<Navigate to="/login" replace />
}
return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};
export default Auth;