Saltar al contenido
Under the Hood
Available in Spanish only

React

useEffect() vs useMemo(): ¿cuándo y cómo se elige cada uno?

Última revisión:

React
  • Rendimiento
  • Renderizado
  • Estado

Respuesta corta

useEffect() sincroniza el componente con algo externo después del render y no produce un valor para mostrar.

useMemo() calcula un valor durante el render y lo guarda en caché. Si un dato se puede derivar de props o estado, se calcula en el render, nunca con un efecto.

En esta página

Explicación

Dos propósitos distintos

Aunque los dos reciben una función y un array de dependencias, useEffect y useMemo resuelven problemas que no tienen nada que ver entre sí:

  • useEffect sirve para sincronizar el componente con algo que está fuera de React: el DOM, un temporizador, una suscripción, la red o el título de la pestaña.
  • useMemo sirve para cachear el resultado de un cálculo puro entre renders, de modo que no se repita si sus datos de entrada no cambiaron.

Por eso no son intercambiables. Cuando alguien duda entre uno y otro, casi siempre el problema real es otro: está intentando calcular un dato con un efecto.

Cuándo se ejecuta cada uno

React trabaja en dos fases. En el render llama a mi componente y calcula el JSX; en el commit aplica los cambios al DOM.

  • useMemo corre durante el render, de forma síncrona. Su resultado está disponible en la misma ejecución del componente, listo para usarse en el JSX.
  • useEffect corre después del commit, normalmente después de que el navegador pinta la pantalla. Para cuando se ejecuta, el usuario ya vio el resultado del render.

Qué devuelve cada uno

  • useMemo devuelve el valor calculado. Si las dependencias no cambiaron, devuelve el mismo valor (y la misma referencia) que en el render anterior.
  • useEffect no devuelve nada. Pero la función que le paso puede devolver una función de limpieza (cleanup), que React ejecuta antes de volver a correr el efecto y cuando el componente se desmonta. Ahí cancelo suscripciones, limpio timers o restauro lo que haya cambiado.

Comparación rápida

useEffect useMemo
Propósito Sincronizar con algo externo Cachear un cálculo puro
Cuándo corre Después del commit Durante el render
Qué devuelve Nada (la función puede devolver un cleanup) El valor calculado
Sin array de dependencias Corre después de cada render No aplica: el array es obligatorio
Con [] Corre una vez al montar Calcula una vez
Con [a, b] Corre cuando cambia a o b Recalcula cuando cambia a o b
Efectos secundarios Es justo para eso Prohibidos: la función debe ser pura
Si lo quito El componente deja de sincronizarse Todo funciona igual, solo más lento

Cuándo sí y cuándo no

Antes de escribir cualquiera de los dos, me hago estas preguntas:

  1. ¿Toca algo fuera de React? (DOM, timers, suscripciones, localStorage, red). Entonces es un useEffect.
  2. ¿Es un dato que sale de props o estado? Entonces lo calculo directamente en el render, sin efecto y sin estado extra.
  3. ¿Ese cálculo es costoso o necesito una referencia estable? Recién ahí lo envuelvo en useMemo.
  4. ¿Responde a algo que hizo el usuario? (un clic, un envío de formulario). Eso va en el event handler, no en un efecto.

La documentación oficial lo explica muy bien en You Might Not Need an Effect.

Código

El caso: un buscador que filtra una lista de productos según lo que escribe el usuario y muestra la cantidad de resultados en el título de la pestaña.

Antipatrón

WithEffect.tsx
import { useEffect, useState } from 'react';
type Product = { id: string; name: string };
type Props = { products: Product[] };
export function ProductSearch({ products }: Props) {
const [query, setQuery] = useState('');
// Estado redundante: se puede calcular a partir de products y query
const [filtered, setFiltered] = useState<Product[]>([]);
useEffect(() => {
setFiltered(products.filter((p) => p.name.toLowerCase().includes(query.toLowerCase())));
}, [products, query]);
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<ul>
{filtered.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</>
);
}

Con useMemo

WithMemo.tsx
import { useEffect, useMemo, useState } from 'react';
type Product = { id: string; name: string };
type Props = { products: Product[] };
export function ProductSearch({ products }: Props) {
const [query, setQuery] = useState('');
// Dato derivado: se calcula en el render y se cachea
const filtered = useMemo(
() => products.filter((p) => p.name.toLowerCase().includes(query.toLowerCase())),
[products, query],
);
// Efecto real: sincroniza con algo fuera de React (la pestaña del navegador)
useEffect(() => {
const previousTitle = document.title;
document.title = `${filtered.length} resultados`;
// Cleanup: restaura el título al cambiar o desmontar
return () => {
document.title = previousTitle;
};
}, [filtered.length]);
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<ul>
{filtered.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</>
);
}

Por qué el primero es un antipatrón: cada vez que cambia query, React renderiza con la lista anterior, pinta, ejecuta el efecto, llama a setFiltered y vuelve a renderizar. Son dos renders por cada tecla, hay un frame con datos desactualizados y además tengo un estado que puede quedar desincronizado.

Por qué el segundo está bien: cada hook cumple su rol. useMemo calcula la lista durante el render, así que siempre es coherente con query. useEffect solo hace lo que no se puede hacer en el render: tocar document.title, y deja el título como estaba en el cleanup.

Si la lista tiene pocas decenas de productos, ni siquiera necesito useMemo: basta con const filtered = products.filter(...) en el render. El memo se justifica cuando la lista es grande o el filtro es caro.

Trade-offs

El costo de useMemo

useMemo no es gratis: guarda el valor en memoria, compara las dependencias en cada render y suma código que hay que mantener. Lo uso en dos casos:

  • Cálculos costosos, y medidos, no supuestos. La guía de react.dev sugiere medir con console.time: si el cálculo toma alrededor de 1 ms o más, vale la pena memoizarlo.
  • Estabilidad referencial, cuando paso un objeto o array a un hijo envuelto en memo o lo uso como dependencia de otro hook.

Además, es una optimización, no una garantía: React puede descartar la caché y recalcular. Mi código tiene que funcionar igual si quito el useMemo, y la función que le paso tiene que ser pura.

Los riesgos de useEffect

  • Dependencias mal declaradas: si olvido una, el efecto trabaja con valores desactualizados. Si incluyo un objeto que se recrea en cada render, el efecto corre siempre, y si ese efecto actualiza estado, puedo terminar en un bucle infinito.
  • Fetch dentro de un efecto: funciona, pero tengo que manejar las condiciones de carrera (un flag ignore o un AbortController en el cleanup). En una app real prefiero que lo resuelva el framework o una librería de datos.
  • Usarlo para derivar datos o reaccionar a eventos: es el error más común, y casi siempre se resuelve calculando en el render o moviendo la lógica al event handler.

Entradas relacionadas

  • React

    Hooks de React: cómo un componente recuerda entre renders

    Los hooks son funciones que se enganchan a React para que un componente de función recuerde valores entre renders, lea contexto o se sincronice con algo externo, cosas que antes solo podían hacer las clases. Se llaman siempre en el mismo orden y en el nivel superior del componente, porque React los identifica por ese orden.

    React
  • React

    ¿Qué es un componente? Lo aplicamos en React y Blazor

    Un componente es una pieza encapsulada y reutilizable de la interfaz: recibe datos desde afuera, puede tener estado propio y describe cómo debe verse la UI para esos datos. Renderizarlo es ejecutarlo para obtener esa descripción; luego el framework la compara con la anterior y actualiza en el DOM solo lo que cambió.

    General