`useEffect` no es tu primera opción. Es tu último recurso.
Todo dev de React aprende useEffect temprano y luego pasa años usándolo mal.
No es tu culpa. El nombre parece suficientemente genérico como para encajar en todo. Pero la API tiene un contrato específico: sincronizar con el mundo externo a React. DOM, network, browser APIs.
Si no estás cruzando esa frontera, probablemente no necesitas un efecto.
El estado derivado no necesita efecto
El error más común. Un valor depende de otro estado, entonces los sincronizas:
// Don't do thisconst [items, setItems] = useState([]);const [count, setCount] = useState(0); useEffect(() => { setCount(items.length);}, [items]);Dos renderizados para algo que puedes calcular directamente. React renderiza con count desactualizado, el efecto corre, actualiza estado y React renderiza de nuevo.
Simplemente calcula:
// Do this insteadconst [items, setItems] = useState([]);const count = items.length;Sin estado extra. Sin efecto. Sin render extra.
Filtrar datos no necesita efecto
Mismo error, distinto formato:
// Don't do thisconst [query, setQuery] = useState("");const [filtered, setFiltered] = useState(todos); useEffect(() => { setFiltered(todos.filter((t) => t.text.includes(query)));}, [todos, query]);Dos renderizados por cambio. Calcula inline:
// Do this insteadconst [query, setQuery] = useState("");const filtered = todos.filter((t) => t.text.includes(query));Si el cálculo es pesado, usa useMemo. Cachea el resultado en lugar de agendar updates de estado como useEffect + setState:
const filtered = useMemo( () => todos.filter((t) => t.text.includes(query)), [todos, query]);Responder a eventos no necesita efecto
¿El usuario hizo algo? Respóndelo en el handler:
// Don't do thisconst [submitted, setSubmitted] = useState(false); useEffect(() => { if (submitted) { sendAnalytics("form_submit"); setSubmitted(false); }}, [submitted]); function handleSubmit() { setSubmitted(true);}Esa es una forma indirecta de decir "cuando el usuario envíe, dispara analytics". Ponlo directo en el handler:
// Do this insteadfunction handleSubmit() { sendAnalytics("form_submit");}Regla simple: acción del usuario -> handler. El componente aparece y necesita sincronizar con algo externo -> efecto.
Resetear estado al cambiar prop no necesita efecto
// Don't do thisfunction ProfilePage({ userId }) { const [comment, setComment] = useState(""); useEffect(() => { setComment(""); }, [userId]); // ...}Primero renderiza estado desactualizado, luego el efecto lo limpia. Usa key:
// Do this instead// In the parent:<ProfilePage userId={id} key={id} />key distinto = nueva instancia. El estado inicia limpio, sin código extra.
Cuándo useEffect sí es la respuesta correcta
- Event listeners (
resize,scroll, WebSockets) - Integraciones con librerías que manipulan el DOM directamente
- Fetch de datos, pero prefiere SWR o TanStack Query porque resuelven race conditions que seguramente vas a ignorar
// This is a valid use of useEffectuseEffect(() => { const handler = () => setWindowWidth(window.innerWidth); window.addEventListener("resize", handler); return () => window.removeEventListener("resize", handler);}, []);La pregunta que lo resuelve todo
Antes de escribir useEffect:
¿Estoy sincronizando con algo fuera de React?
- ¿Calculando un valor a partir de estado o props? Calcula durante el render.
- ¿Cálculo pesado? Usa
useMemo. - ¿Respondiendo a una acción del usuario? Usa un handler.
- ¿Necesitas resetear estado cuando cambia una prop? Usa
key.
useEffect es una válvula de escape, no el patrón por defecto.