Estoy intentando usar el gancho useEffect de React para ejecutar un comando de búsqueda y almacenar el resultado en el estado. Luego me gustaría usar ese valor de estado para representar condicionalmente un componente React Route. Tengo problemas para establecer el valor de esa variable en el estado antes de que el componente ejecute la instrucción condicional y devuelva el componente Ruta.
Código expreso:
app.post('/refresh_token_from_db', async (req: Request, res: Response) => { res.send(true) })Rutas protegidas:
import { Navigate, Outlet } from 'react-router-dom' import { useState, useEffect } from 'react'; function ProtectedRoutes() { const [token, setToken] = useState<boolean>(false); // Data does't start loading // until *after* component is mounted useEffect(() => { const go = async () => { await fetch('/refresh_token_from_db', { method: 'POST', mode: 'same-origin', redirect: 'follow', credentials: 'same-origin', headers: { 'content-type': 'application/json', 'Accept': 'application/json' }, }).then(res => res.json()) .then(data => { console.log("Should run 1st", data); setToken(data) }) } go() }, []); console.log("Should run 2nd", token); return ( token ? <Outlet /> : <Navigate to="/login" /> ); } export default ProtectedRoutesSi lo entiendo correctamente, desea que su componente espere la respuesta de búsqueda y luego decida si lo redirigirá para iniciar sesión o no.
En ese caso, agregue otro estado llamado isWaiting y configúrelo inicialmente como verdadero, luego llame a setIsWaiting(false) justo debajo de setToken(data);
Finalmente, cambia el retorno a esto:
return ( token && !isWaiting ? <Outlet /> : <Navigate to="/login" /> );Obtener los datos, así como ejecutar la devolución de llamada de un useEffect y mutar el state en React son tareas asincrónicas. Lo que significa este código:
return ( token ? <Outlet /> : <Navigate to="/login" /> ); podría ejecutarse antes, y cuál está causando esa redirección anterior. Lamentablemente, aquí no se puede dictar el orden de ejecución, o muy difícilmente. Una solución es usar un estado adicional, lo llamé checking , así:
import { useEffect, useState } from 'react'; import { Navigate, Outlet } from 'react-router-dom'; function ProtectedRoutes() { const [token, setToken] = useState<boolean>(false); const [checking, setIsChecking] = useState<boolean>(true); // Data does't start loading // until *after* component is mounted useEffect(() => { const go = async () => { await fetch('/refresh_token_from_db', { method: 'POST', mode: 'same-origin', redirect: 'follow', credentials: 'same-origin', headers: { 'content-type': 'application/json', 'Accept': 'application/json' }, }).then(res => res.json()) .then(data => { console.log("Should run 1st", data); setToken(data) setIsChecking(false) }) } go() }, []); console.log("Should run 2nd", token); if(checking) return <p>Loading...</p> return ( token ? <Outlet /> : <Navigate to="/login" /> ); } export default ProtectedRoutes Su estado de token inicial enmascara o coincide con el estado "no autenticado" y es por eso que el componente Navigate se procesa en el ciclo de procesamiento inicial y navega a la ruta de inicio de sesión.
No utilice el mismo estado inicial que los valores de estado "autenticados" o "no autenticados". Use este valor de estado "indeterminado" para convertir condicionalmente en nulo o algún indicador de carga. Además, no mezcle async/await con las cadenas Promise, se considera antipatrón. Tenga en cuenta que fetch puede devolver Promesas rechazadas y debe manejarlas en consecuencia.
Ejemplo:
function ProtectedRoutes() { const [token, setToken] = useState<boolean | undefined>(); // <-- initially undefined // Data does't start loading // until *after* component is mounted useEffect(() => { const go = async () => { try { const response = await fetch('/refresh_token_from_db', { method: 'POST', mode: 'same-origin', redirect: 'follow', credentials: 'same-origin', headers: { 'content-type': 'application/json', 'Accept': 'application/json' }, }); const data = await response.json(); setToken(data); } catch(error) { // catch/handle any errors // set unauthenticated status setToken(false); } }; go(); }, []); if (token === undefined) { return null; // or loading indicator/spinner/etc... } return token ? <Outlet /> : <Navigate to="/login" replace />; }