Skip to content
This page has been auto-translated and may contain errors.View in English

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:

jsx
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.

Las condiciones de carrera entre solicitudes superpuestas son la arista afilada que vale la pena nombrar directamente. Si url cambia rápidamente, alguien escribiendo en un cuadro de búsqueda o haciendo clic entre perfiles, el componente dispara una nueva solicitud antes de que la anterior se haya resuelto, y las respuestas de red no llegan de forma confiable en el orden en que se enviaron. Sin el flag de limpieza, una respuesta lenta a la primera solicitud puede llegar después de una respuesta rápida a la segunda, sobrescribiendo data con contenido obsoleto para una url que ni siquiera estás mostrando ya. Eso es exactamente lo que active previene: cada ejecución del efecto cierra sobre su propia variable active, así que una solicitud de una ejecución anterior nunca puede escribir en el estado del renderizado actual una vez que su propia limpieza se ha ejecutado.

AbortController es la otra herramienta para esto, y va más lejos: cancela la solicitud en vuelo en sí misma en lugar de solo ignorar una respuesta obsoleta que llega después.

jsx
useEffect(() => {
  const controller = new AbortController()
  setData(null)
  setError(null)
  fetch(url, { signal: controller.signal })
    .then(r => {
      if (!r.ok) throw new Error(`Request failed: ${r.status}`)
      return r.json()
    })
    .then(setData)
    .catch(e => { if (e.name !== 'AbortError') setError(e) })
  return () => controller.abort()
}, [url])

Esta versión todavía necesita las mismas dos correcciones que el efecto anterior: restablecer data y error en la parte superior de la ejecución, para que un reintento pueda realmente limpiar un fracaso anterior o un perfil obsoleto, y verificar r.ok antes de analizar el cuerpo, ya que fetch trata un 404 o 500 como una solicitud completada en lugar de un rechazo. AbortController solo reemplaza el trabajo que estaba haciendo el flag de limpieza, cancelando una solicitud que ya no se desea, así que no hace nada sobre cómo el estado se lleva entre ejecuciones o cómo fetch trata una respuesta HTTP fallida por sí solo. Ambos enfoques solucionan el problema de ordenamiento, pero abortar también ahorra a la red el problema de terminar una solicitud que nadie quiere ya.

La dirección más nueva, a partir de React 19, es leer datos asincronos con el hook use dentro de un componente envuelto en <Suspense>, que deja que React maneje el estado pendiente y de carga a nivel del framework en lugar de hacerlo a mano en cada componente. use no se usa típicamente por sí solo. Generalmente se empareja con un framework o una biblioteca de datos que gestiona el caché y el ciclo de vida de la solicitud debajo de él. Beyond the basics cubre esto junto con el resto del ecosistema más amplio de obtención de datos.

JunoFetch en un efecto, observa los estados El patrón a recordar: pon el fetch dentro de 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.

JunoFetch en un efecto, observa los estados Haz fetch dentro de useEffect, almacena el resultado y cualquier error en estado, y renderiza directamente de esos en lugar de agregar un booleano de carga separado si no lo necesitas.

Devuelve una función de limpieza que voltea un flag active, para que una respuesta obsoleta de una url anterior o un componente desmontado no pueda sobrescribir el estado actual.

Más allá de una solicitud ocasional, recurre a React Query o la carga de datos de tu framework en lugar de hacerlo manualmente en todas partes.

JunoFetch en un efecto, observa los estados Cada ejecución del efecto es dueña de su propio cierre active, que es lo que la mantiene segura contra respuestas fuera de orden cuando url cambia rápidamente; AbortController hace el mismo trabajo y también cancela la solicitud desperdiciada.

Trata el fetch raw de useEffect como el mecanismo que vale la pena entender una vez. Las aplicaciones reales se apoyan en una biblioteca de datos o el hook use con Suspense para solicitudes reales, ambos construidos sobre exactamente este patrón debajo.

Próximo: Refs y el DOM, la compuerta de escape para los trabajos que necesitan el nodo del DOM en sí.