CodeForge

React 19 de Cero a Experto / Efectos y ciclo de vida

Cargar datos con useEffect (y la race condition)

Teoría24 min20 XP

Cargar datos de una API cuando un componente aparece es el caso rey de useEffect. Parece simple: un fetch en un efecto con [], guardas el resultado en estado, listo. Pero esconde dos cosas que todos aprenden a los golpes: el patrón de TRES ESTADOS (cargando / error / datos) que vas a escribir mil veces, y la RACE CONDITION —cuando dos cargas se pisan y muestras datos viejos—. Hoy armas el patrón bien desde el principio.

Los tres estados de una carga

El patrón, con su trampa incluida

Este playground carga una lista (simulamos la API con una promesa para que sea determinista) usando los tres estados Y la bandera de cancelación que evita la race condition:

Los tres estados (cargando, error, gastos) cubren los tres momentos, y los guardas antes de cada return según en cuál estés. Lo sutil es la bandera ignorar: el cleanup la pone en true cuando el efecto se descarta, y cada setEstado la revisa (if (!ignorar)). Así, si llega una respuesta de una carga que ya no importa, la ignoras en vez de pisar datos nuevos con datos viejos.

La race condition, en detalle

Con fetch real y AbortController

En una app real, obtenerGastos es un fetch. El patrón es idéntico, y puedes además CANCELAR la petición en vuelo con AbortController (SC-02, M10):

fetch-real.tsx
useEffect(() => {
const controlador = new AbortController();

async function cargar() {
  try {
    const res = await fetch("/api/gastos", { signal: controlador.signal });
    if (!res.ok) throw new Error(`HTTP ${res.status}`); // ¡recuerda: 404/500 NO rechazan!
    const datos = (await res.json()) as Gasto[];
    setGastos(datos);
  } catch (e) {
    if (e instanceof Error && e.name !== "AbortError") setError(e.message);
  } finally {
    setCargando(false);
  }
}

cargar();
// CLEANUP: aborta la petición en vuelo si el efecto se repite o el componente se desmonta
return () => controlador.abort();
}, []);

Aquí el cleanup ABORTA la petición (mejor aún que ignorar la respuesta: ni siquiera la esperas), y el catch ignora el AbortError porque cancelar no es un error real —justo como en el buscador de países del SC-02—. Nota el if (!res.ok) throw: fetch no rechaza en 404/500, tienes que lanzarlo tú.

¿Por qué se usa una bandera 'ignorar' (o AbortController) en el cleanup al cargar datos en un efecto?

Mini-reto

Arma el patrón completo: 1) escribe una función obtener(): Promise<string[]> que resuelva tras setTimeout con una lista de nombres; 2) en un componente, cárgala en un useEffect con [] usando los tres estados (cargando, error, datos) y la bandera ignorar en el cleanup; 3) muestra "Cargando…", un mensaje de error, o la lista según el estado. Bonus: agrega un botón que reinicie la carga cambiando una key o un contador en las dependencias, y comprueba que la bandera evita cualquier pisada.

Qué sigue

Fíjate cuánto código repetitivo lleva una carga: tres estados, un efecto, la bandera, el manejo de error… y esto se repite en CADA componente que consuma datos. La próxima lección resuelve eso con los CUSTOM HOOKS: empaquetar lógica con estado y efectos en una función reutilizable (useFetch, useLocalStorage) que escribes una vez y usas en todas partes. Es donde React se vuelve elegante.