useMemo y useCallback: claves para no sobre‑optimizar

En el día a día de un desarrollador React es fácil caer en la tentación de optimizar prematuramente. Los hooks useMemo y useCallback son herramientas poderosas, pero si se usan sin medir su impacto pueden terminar siendo una carga más que una ayuda.
¿Qué hacen exactamente?
useMemo memoiza el resultado de una función de cálculo y lo vuelve a ejecutar sólo cuando cambian sus dependencias. useCallback hace lo mismo, pero con funciones: devuelve la misma referencia de la función mientras sus dependencias no varíen.
Cuándo realmente aportan rendimiento
- Cálculos costosos: si la operación implica loops extensos, consultas a APIs locales o procesamiento de datos grande, memoizar el resultado evita repetir trabajo en cada render.
- Componentes hijos que dependen de la referencia: cuando pasas una función como prop a un componente
React.memoo a un hook comouseEffect, una referencia estable evita renders innecesarios. - Listas largas o tablas: al generar filas con
Array.map, memoizar la lista procesada puede reducir el tiempo de renderizado.
Señales de que no los necesitas
- El cálculo es trivial (una suma o una concatenación).
- El componente se renderiza pocas veces (p. ej. una pantalla de configuración que el usuario abre una sola vez).
- Las dependencias cambian en casi cada render, lo que invalida la memoización de inmediato.
- El uso de
useCallbackhace que el código sea más difícil de leer sin aportar beneficio visible.
Ejemplo práctico
Imagina una lista de productos filtrada por una búsqueda. El filtrado es costoso porque implica comparar varias propiedades de cada objeto.
import React, { useState, useMemo, useCallback } from 'react';
function ProductList({ products }) {
const [query, setQuery] = useState('');
// Memoizamos el resultado del filtrado
const filtered = useMemo(() => {
console.log('Filtrando productos...');
return products.filter(p =>
p.name.toLowerCase().includes(query.toLowerCase())
);
}, [products, query]);
// La función de click se mantiene estable para evitar renders de hijos memoizados
const handleClick = useCallback(id => {
console.log('Producto seleccionado:', id);
}, []);
return (
setQuery(e.target.value)}
/>
{filtered.map(p => (
))}
);
}
const ProductItem = React.memo(function ({ product, onSelect }) {
console.log('Render Item', product.id);
return (
onSelect(product.id)}>{product.name}
);
});
export default ProductList;
En este caso useMemo evita recalcular la lista filtrada cuando el usuario escribe en otro campo que no sea la búsqueda, y useCallback garantiza que onSelect tenga la misma referencia, evitando que ProductItem se vuelva a renderizar sin necesidad.
Cómo medir el impacto
Antes de añadir memoización, abre la React DevTools y observa la frecuencia de renders. Luego, introduce useMemo o useCallback y vuelve a medir. Si el número de renders disminuye y el tiempo de CPU se reduce, has ganado algo. Si no, es mejor retirar el hook y mantener el código más sencillo.
Buenas prácticas resumidas
- Memoiza solo cálculos que realmente consumen recursos.
- Usa
useCallbackcuando pases funciones a componentes memoizados o a efectos que dependan de la referencia. - Mantén la lista de dependencias lo más pequeña posible; incluye únicamente lo necesario.
- No añadas hooks por moda; prueba y verifica con herramientas de profiling.
- Recuerda que la legibilidad del código siempre pesa más que micro‑optimizaciones.
En resumen, useMemo y useCallback son como una navaja suiza: útiles en situaciones concretas, pero peligrosas si intentas usarlas en todo. Aprende a identificar los cuellos de botella, mide, y aplica estos hooks con criterio. Tu código será más rápido y, lo que es igual de importante, más fácil de mantener.