Obtener datos
Un componente que muestra datos de un servidor no tiene esos datos en el momento en que se renderiza por primera vez. Tiene que pedirlos, esperar, y luego mostrar algo cuando la respuesta llega, o si la solicitud falla en el camino. Este capítulo cubre el patrón que usan las apps React para eso: fetch dentro de un efecto, y mantener el resultado, un estado de carga, y un error en useState.
Aquí hay un componente Profile que obtiene un usuario por URL. El renderizado sucede primero, y comunicarse con el servidor justo después es exactamente para qué 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 cero en Async JavaScript; todo aquí se construye directamente sobre eso.
useEffect es el hook que ejecuta código después de que React ha renderizado un componente, en lugar de 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 nuevamente cuando cambien, aquí 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. Eso importa por dos razones: sin eso, un cambio de url seguiría mostrando el perfil antiguo en lugar de "Loading...", ya que data todavía contiene el último resultado exitoso, y una solicitud que tiene éxito después de una anterior que falló nunca limpiaría el error antiguo, ya que nada jamás establece error nuevamente 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 sigue resolviendo normalmente como un objeto de respuesta, por eso el primer .then verifica r.ok y lanza 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. Abajo en el JSX, error y data se verifican como cualquier otro estado: un error renderiza un mensaje, ningún dato aún renderiza un mensaje de carga, y los datos renderizan la interfaz de usuario real.
No hay una variable de carga separada aquí. Mientras data y error sigan siendo null, el componente está entre el momento en que la solicitud sale y algo regresa, así que if (!data) return <p>Loading...</p> cubre esa brecha por sí solo. Algunos componentes sí agregan un boolean 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 una bandera de limpieza. useEffect puede devolver una función, y React llama a esa función justo antes de que el efecto se ejecute nuevamente 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 url cambia, o cuando el componente sale de la pantalla completamente, eventualmente se resolvería e intentaría actualizar un estado que ya no pertenece a lo que se está mostrando. La bandera convierte una respuesta tardía en un no-op en lugar de un bug.
Obtener datos a mano así vale la pena entenderlo, porque es el patrón sobre el que casi todo lo demás se construye. En una app real generalmente recurrirás a una librería de obtención de datos como React Query, o la carga de datos integrada de un framework (Next.js y frameworks similares manejan esto en la capa de routing), en lugar de escribir useEffect y algunos estados para cada solicitud. Esas herramientas manejan el caching, reintentos, y los problemas de timing para ti. El patrón en este capítulo es sobre lo que están construidas.
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 aún están vacíos. La bandera active evita que una respuesta tardía establezca estado en un componente que ya no está mostrando esos datos. Lo siguiente: Refs y el DOM, la salida de emergencia para los trabajos que necesitan el nodo del DOM en sí.

