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

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:

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

Las condiciones de carrera entre solicitudes superpuestas son el borde afilado que vale la pena nombrar directamente. Si url cambia rápidamente, alguien escribiendo en una caja de búsqueda, o haciendo clic entre perfiles, el componente dispara una nueva solicitud antes de que la anterior se resuelva, y las respuestas de red no llegan confiablemente en el orden en que fueron enviadas. Sin la bandera 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. 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 allá: cancela la solicitud en vuelo en sí misma en lugar de solo ignorar una respuesta obsoleta que llega más tarde.

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 aún necesita las mismas dos correcciones que el efecto anterior: reiniciar data y error en la parte superior de la ejecución, así un reintento puede realmente limpiar un fallo anterior o un perfil obsoleto, y verificar r.ok antes de parsear 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 hacía la bandera de limpieza, cancelando una solicitud que ya no se desea, así que no hace nada acerca de cómo el estado se lleva entre ejecuciones o cómo fetch trata una respuesta HTTP fallida por sí solo. Ambos enfoques arreglan el problema de ordenamiento, pero abortar también ahorra a la red la molestia de terminar una solicitud que nadie ya quiere.

La dirección más nueva, a partir de React 19, es leer datos asíncronos con el hook use dentro de un componente envuelto en <Suspense>, que permite que React maneje el estado pendiente y de carga a nivel del framework en lugar de a mano en cada componente. use típicamente no se usa solo. Generalmente se empareja con un framework o una librería de datos que maneja caching y el ciclo de vida de la solicitud debajo de esto. Beyond the basics retoma 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 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.
JunoFetch en un efecto, observa los estados Fetch dentro de useEffect, almacena el resultado y cualquier error en estado, y renderiza directamente basándote en esos en lugar de agregar un boolean de carga separado si no lo necesitas. Devuelve una función de limpieza que voltea una bandera active, así una respuesta obsoleta de una url anterior o un componente desmontado no puede sobrescribir el estado actual. Pasada una solicitud única, recurre a React Query o la carga de datos de tu framework en lugar de hacer esto a mano en todas partes.
JunoFetch en un efecto, observa los estados Cada ejecución del efecto posee su propio cierre active, que es lo que la mantiene segura contra respuestas desordenadas cuando url cambia rápidamente; AbortController hace el mismo trabajo y también cancela la solicitud desperdiciada. Trata el fetch crudo de useEffect como el mecanismo que vale la pena entender una vez. Las apps reales dependen de una librería de datos o el hook use con Suspense para las solicitudes reales, ambos construidos exactamente sobre este patrón debajo.

Lo siguiente: Refs y el DOM, la salida de emergencia para los trabajos que necesitan el nodo del DOM en sí.