Obtener datos
Un componente que muestra datos del servidor no tiene esos datos en el momento en que se renderiza por primera vez. Tiene que pedirlos, esperar y luego mostrar algo cuando llega la respuesta, o si la solicitud falla en el camino. Este capítulo cubre el patrón que usan las aplicaciones React para esto: hacer fetch dentro de un efecto y guardar el resultado, un estado de carga y un error en useState.
Aquí hay un componente Profile que obtiene un usuario por URL. El renderizado ocurre primero, y hacer la solicitud al servidor justo después es exactamente el momento para el que existe useEffect:
function Profile({ url }) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
useEffect(() => {
let active = true
setData(null)
setError(null)
fetch(url)
.then(r => {
if (!r.ok) throw new Error(`Request failed: ${r.status}`)
return r.json()
})
.then(d => { if (active) setData(d) })
.catch(e => { if (active) setError(e) })
return () => { active = false }
}, [url])
if (error) return <p>Failed to load</p>
if (!data) return <p>Loading...</p>
return <h1>{data.name}</h1>
}Si fetch, promesas o .then todavía te resultan desconocidos, el manual de JavaScript los cubre desde el principio en JavaScript asincrónico; todo aquí se construye directamente sobre eso.
useEffect es el hook que ejecuta código después de que React haya renderizado un componente, en lugar de hacerlo durante el renderizado mismo. Le pasas una función para ejecutar y un array de dependencias, la lista de valores que deberían hacer que se ejecute de nuevo cuando cambien; en este caso solo url. Profile comienza con dos piezas de estado: data para el resultado y error para cualquier cosa que salga mal. En la parte superior del efecto, setData(null) y setError(null) limpian lo que dejó la ejecución anterior. Esto importa por dos razones: sin ello, un cambio de url seguiría mostrando el perfil anterior en lugar de "Loading...", porque data todavía contiene el último resultado exitoso, y una solicitud que tiene éxito después de que una anterior falló nunca limpiaría el error anterior, porque nada vuelve a establecer error en null. Luego fetch devuelve una promesa para la respuesta. fetch solo rechaza esa promesa en caso de fallo de red, así que un 404 o 500 se resuelve normalmente como un objeto de respuesta, por eso el primer .then verifica r.ok y lanza una excepción antes de que la respuesta llegue a .json(). Si omites esa verificación, una solicitud fallida fluiría directamente a data como si hubiera tenido éxito. Una vez que la verificación pasa, .json() lee el cuerpo y el resultado llega a data a través de setData. Si algo de esto se rechaza, .catch pone el problema en error en su lugar. Más abajo en el JSX, se verifican error y data como cualquier otro estado: un error renderiza un mensaje, sin datos aún renderiza un mensaje de carga, y los datos renderizan la interfaz real.
No hay una variable de carga separada aquí. Mientras data y error sigan siendo ambos null, el componente está entre que la solicitud se envía y que algo regresa, así que if (!data) return <p>Loading...</p> cubre ese vacío por sí solo. Algunos componentes sí agregan un booleano isLoading dedicado para un control más fino, pero derivar la carga del estado que ya tienes es a menudo más simple, y es un valor menos para mantener sincronizado.
La variable active dentro del efecto es un flag de limpieza. useEffect puede devolver una función, y React llama a esa función justo antes de que el efecto se ejecute de nuevo y cuando el componente se desmonta. Aquí, la función devuelta establece active en false, y tanto .then como .catch la verifican antes de llamar a setData o setError. Sin esa verificación, una solicitud aún en vuelo cuando cambia url, o cuando el componente desaparece de la pantalla por completo, eventualmente se resolvería e intentaría actualizar un estado que ya no pertenece a lo que se muestra. El flag convierte una respuesta tardía en un no-op en lugar de un bug.
Hacer fetch a mano así vale la pena entender, porque es el patrón sobre el que se construye casi todo lo demás. En una aplicación real normalmente recurriría a una biblioteca de obtención de datos como React Query, o a la carga de datos integrada de un framework (Next.js y frameworks similares manejan esto en la capa de enrutamiento), en lugar de escribir useEffect y algunos estados para cada solicitud. Esas herramientas manejan el caché, reintentos y los problemas de tiempo que siguen para ti. El patrón en este capítulo es sobre lo que se construyen.
useEffect, mantén data y error en estado, y verifícalos en tu JSX para decidir qué mostrar. La carga es el momento en que ambos todavía están vacíos. El flag active evita que una respuesta tardía establezca estado en un componente que ya no muestra esos datos.
Próximo: Refs y el DOM, la compuerta de escape para los trabajos que necesitan el nodo del DOM en sí.

