¿Para qué sirven useMemo y useCallback? ¿Cuándo los usarías y cuándo serían una optimización prematura?
Ver respuesta — intenta responderla en voz alta primero
useMemo memoriza el RESULTADO de un cálculo y useCallback memoriza una FUNCIÓN, y ambos conservan la misma referencia entre renders mientras sus dependencias no cambien. Se usan cuando un cálculo es caro o cuando la igualdad referencial importa (por ejemplo, con React.memo o dependencias de un effect); en el resto de casos suelen ser ruido.
En cada render, el componente se ejecuta de nuevo: todos los objetos, arreglos y funciones que declaras dentro se crean OTRA VEZ, con referencias nuevas. Para JavaScript, () => {} !== () => {}.
Los puntos clave:
- useMemo(fn, deps): ejecuta
fny guarda su resultado. En renders siguientes, si las dependencias no cambiaron, devuelve el valor guardado sin recalcular. - useCallback(fn, deps): guarda la función misma.
useCallback(fn, deps)equivale auseMemo(() => fn, deps). - Igualdad referencial: React.memo, los arreglos de dependencias y comparaciones internas usan
Object.is. Un objeto o función "nuevos" en cada render rompen esas comparaciones aunque su contenido sea idéntico. - Cuándo SÍ: cálculos realmente costosos (filtrar/ordenar listas grandes), o valores/funciones que se pasan a un hijo envuelto en
React.memoo que son dependencia de unuseEffect. - Cuándo NO: memorizar sumas triviales, strings o handlers que van a un
<button>normal. Cada memo tiene costo (memoria y comparación de deps) y ensucia el código. Primero mide, luego optimiza. - Relación con React.memo: memorizar el callback solo sirve si el hijo que lo recibe está envuelto en
React.memo; si no, el hijo se re-renderiza de todas formas.
import { memo, useCallback, useMemo, useState } from "react";
type Producto = { id: number; nombre: string; precio: number };
type FilaProps = { producto: Producto; onSeleccionar: (id: number) => void };
// React.memo: solo se re-renderiza si sus props cambian de referencia/valor
const Fila = memo(function Fila({ producto, onSeleccionar }: FilaProps) {
return (
<li onClick={() => onSeleccionar(producto.id)}>
{producto.nombre} — ${producto.precio}
</li>
);
});
function Catalogo({ productos }: { productos: Producto[] }) {
const [filtro, setFiltro] = useState("");
const [seleccionado, setSeleccionado] = useState<number | null>(null);
// useMemo: solo re-filtra cuando cambian productos o filtro,
// no cuando cambia "seleccionado"
const visibles = useMemo(
() => productos.filter(p => p.nombre.toLowerCase().includes(filtro.toLowerCase())),
[productos, filtro]
);
// useCallback: misma referencia entre renders -> React.memo funciona
const seleccionar = useCallback((id: number) => {
setSeleccionado(id);
}, []);
return (
<div>
<input value={filtro} onChange={e => setFiltro(e.target.value)} />
<p>Seleccionado: {seleccionado ?? "ninguno"}</p>
<ul>
{visibles.map(p => (
<Fila key={p.id} producto={p} onSeleccionar={seleccionar} />
))}
</ul>
{/* Comportamiento esperado: al seleccionar, las filas NO se re-renderizan */}
</div>
);
}
Envolver el hijo en React.memo pero pasarle una función nueva en cada render, anulando la optimización:
// MAL: la arrow function se recrea en cada render del padre,
// así que React.memo en Fila nunca evita nada
<Fila producto={p} onSeleccionar={(id) => setSeleccionado(id)} />
// BIEN: referencia estable con useCallback
const seleccionar = useCallback((id: number) => setSeleccionado(id), []);
<Fila producto={p} onSeleccionar={seleccionar} />
El error opuesto también es común: llenar el componente de useMemo/useCallback "por si acaso". Si nadie compara la referencia y el cálculo es barato, solo agregaste complejidad.
Los dos memorizan cosas entre renders: useMemo guarda el resultado de un cálculo y useCallback guarda la función misma, y ambos mantienen la misma referencia mientras las dependencias no cambien. Importan porque en cada render todo se recrea con referencias nuevas, y eso rompe optimizaciones basadas en igualdad referencial, como React.memo o los arreglos de dependencias. Los uso en dos casos: cálculos costosos, como filtrar listas grandes, y valores o funciones que se pasan a hijos memorizados o a un useEffect. Fuera de eso los considero optimización prematura: tienen su propio costo y prefiero medir antes de memorizar.
Tienes const numeros: number[] con miles de elementos y un estado tema: "claro" | "oscuro" que cambia seguido. Calcula la suma de los números sin que se recalcule cuando cambia el tema.
Ver solución
import { useMemo, useState } from "react";
function Resumen({ numeros }: { numeros: number[] }) {
const [tema, setTema] = useState<"claro" | "oscuro">("claro");
// Solo se recalcula si cambia la referencia de "numeros";
// cambiar el tema reutiliza el valor memorizado
const suma = useMemo(() => numeros.reduce((acc, n) => acc + n, 0), [numeros]);
return (
<div className={tema}>
<p>Suma: {suma}</p>
<button onClick={() => setTema(t => (t === "claro" ? "oscuro" : "claro"))}>Cambiar tema</button>
</div>
);
}