
Race conditions, debouncing, polling y organización de efectos.
Race conditions y abort
- Cuando las respuestas llegan en distinto orden, el estado queda desfasado.
- Se soluciona con
AbortControllero flags de cancelación.
Debouncing
- Evita solicitudes en cada tecla y reduce carga en el servidor.
- Se implementa con un hook que retrasa el valor.
Polling y refetch
- Actualiza datos cada cierto tiempo o bajo demanda.
- Útil para dashboards o notificaciones.
Separación de efectos
- Un efecto por responsabilidad mejora el mantenimiento.
- Ayuda a manejar dependencias sin errores.
Dependencias, cierres y estado desactualizado
- El efecto captura el estado/props del render en el que se definio.
- Si usas valores viejos dentro del efecto, aparece el problema de "stale closure".
- Solucion: agrega dependencias correctas o usa
useRefpara guardar valores mutables.
Async en useEffect
- No declares
useEffectcomoasyncdirectamente. - Define una funcion interna async y manejala con
try/catch. - Usa
AbortControllero un flagignorepara evitar actualizar estado en un efecto cancelado.
Evitar loops de render
- Si un efecto actualiza un estado que es dependencia directa, puede generar loops.
- Verifica si la actualizacion es realmente necesaria antes de llamar a
setState. - Usa condiciones o compara valores previos.
Integrar eventos del DOM
- Registra listeners en montaje y limpia en desmontaje.
- Los handlers deben ser estables para evitar duplicados.
Cuándo crear un custom hook
- Si un efecto se repite en varios componentes, extraelo a un hook.
- Encapsula el estado, el efecto y la interfaz publica del comportamiento.
Ejemplos de Código
5 ejemplos
Cancelación de requests
javascript
1import { useEffect, useState } from "react";
2
3function Buscar({ query }) {
4 const [data, setData] = useState([]);
5
6 useEffect(() => {
7 const controller = new AbortController();
8
9 fetch(`/api/search?q=${query}`, { signal: controller.signal })
10 .then((res) => res.json())
11 .then((json) => setData(json))
12 .catch((error) => {
13 if (error.name !== "AbortError") console.error(error);
14 });
15
16 return () => controller.abort();
17 }, [query]);
18
19 return <ul>{data.map((item) => <li key={item.id}>{item.name}</li>)}</ul>;
20}Debounce reutilizable
javascript
1import { useEffect, useState } from "react";
2
3function useDebounce(value, delay) {
4 const [debounced, setDebounced] = useState(value);
5
6 useEffect(() => {
7 const id = setTimeout(() => setDebounced(value), delay);
8 return () => clearTimeout(id);
9 }, [value, delay]);
10
11 return debounced;
12}Polling con visibilidad
javascript
1import { useEffect, useState } from "react";
2
3function Stats() {
4 const [stats, setStats] = useState(null);
5 const [visible, setVisible] = useState(!document.hidden);
6
7 useEffect(() => {
8 const onVisibility = () => setVisible(!document.hidden);
9 document.addEventListener("visibilitychange", onVisibility);
10 return () => document.removeEventListener("visibilitychange", onVisibility);
11 }, []);
12
13 useEffect(() => {
14 if (!visible) return;
15
16 const cargar = async () => {
17 const res = await fetch("/api/stats");
18 setStats(await res.json());
19 };
20
21 cargar();
22 const id = setInterval(cargar, 5000);
23 return () => clearInterval(id);
24 }, [visible]);
25
26 return <pre>{JSON.stringify(stats, null, 2)}</pre>;
27}Ref para valores actuales
javascript
1import { useEffect, useRef } from "react";
2
3function Logger({ value }) {
4 const latest = useRef(value);
5
6 useEffect(() => {
7 latest.current = value;
8 }, [value]);
9
10 useEffect(() => {
11 const id = setInterval(() => {
12 console.log("Ultimo valor:", latest.current);
13 }, 1000);
14 return () => clearInterval(id);
15 }, []);
16
17 return null;
18}Async con cancelacion
javascript
1import { useEffect, useState } from "react";
2
3function Detalle({ id }) {
4 const [data, setData] = useState(null);
5
6 useEffect(() => {
7 let ignore = false;
8
9 const cargar = async () => {
10 const res = await fetch(`/api/items/${id}`);
11 const json = await res.json();
12 if (!ignore) setData(json);
13 };
14
15 if (id) cargar();
16 return () => {
17 ignore = true;
18 };
19 }, [id]);
20
21 return <pre>{JSON.stringify(data, null, 2)}</pre>;
22}Recursos
3 recursos disponibles
¡Hora de Practicar!
PrácticaPrincipiante15 min
Práctica guiada - Buscador con debounce
Práctica
Input de búsqueda que dispara requests con debounce y cancelación de peticiones.
Desafío de Código
EjercicioPrincipiante15 min
Ejercicios - Patrones con efectos
Ejercicios
(1) Refetch manual con botón, (2) polling configurable, (3) separar efectos por responsabilidad.
Documentación Oficial
DocumentaciónPrincipiante15 min
A Complete Guide to useEffect
Guía avanzada para entender dependencias y cierres.