React 19 de Cero a Experto / Efectos y ciclo de vida
Cargar datos con useEffect (y la race condition)
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):
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.