Auth
este componente de autenticación y funciona bien. Excepto que no redirige si el usuario no autenticado intenta visitar /dashboard
.
El backend al recibir la solicitud /api/me
, conoce al usuario por tener la cookie. Entonces tengo una técnica de autenticación (Cookie-Session).
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>; };
Entonces, el problema principal es que no se realizó la redirección. Además, el user
pasado al contexto no se actualiza correctamente. Tal vez porque estoy confundido acerca de qué usar en useEffect
.
Cualquier ayuda es apreciada.
Hay un par de problemas:
user
.navigate
se llama como un efecto secundario no intencional directamente en el cuerpo del componente. Mueva la llamada de navigate
a un gancho useEffect
o represente el componente Navigate
para ejecutar la acción de navegación imperativa. Utilice un estado de user
inicial undefined
y verifíquelo explícitamente antes de ejecutar la acción de navegación o representar el componente UserContext.Provider
.
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, ie 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
se ejecuta después de que se procesa su JSX, por lo que a medida que se crea su código, en una página, actualice esto if (!user)
que llama a navigate('/login')
siempre pasará, ya que antes de que useEffect
haga su trabajo, user
es null
, ese valor inicial que le diste a useState
. Sin embargo, no está redirigiendo porque la navigate
no funciona dentro de JSX, debe reemplazarse con Navigate
el componente.
Además, en getUser
, tiene esto if (user)
solo después de setUser(data)
, eso no funcionaría bien ya que user
no se actualizará de inmediato, ya que actualizar un state
es una tarea asíncrona que surte efecto después de un re-redner .
Para solucionar sus problemas, puede agregar un estado de checking
, devolver algún cargador mientras se verifica al usuario. También puede optimizar un poco su código en general, como deshacerse de ese estado gotUser
:
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;