Guía Completa de useMemo en React 🪝

¿Qué es useMemo?

useMemo es un Hook de React diseñado para optimizar el rendimiento de tu aplicación memorizando valores computados. Evita cálculos innecesarios entre renderizados al cachear (memorizar) el resultado de una operación costosa.

Sintaxis básica:

jsx
const valorMemorizado = useMemo(() => computacionCostosa(a, b), [a, b]);
  Características principales de useMemo 🚀

  • 🔄 Memoriza el resultado de una función
  • ⚡ Evita recálculos innecesarios
  • 🎯 Solo se recalcula cuando las dependencias cambian
  • 💾 Optimiza el rendimiento en operaciones costosas

Casos ideales para usar useMemo

  Casos para useMemo 🚀

  1. 🧮 Cálculos computacionalmente costosos
  2. 📊 Transformaciones complejas de datos
  3. 🔍 Filtrados o sorting de grandes arrays
  4. 🎨 Generación de elementos visuales complejos
  5. 📏 Prevención de re-renderizados innecesarios con objetos como props.

Ejemplos Prácticos


1. Evitar Cálculos Costosos en Funciones

Si tienes una operación costosa que no necesita recalcularse en cada render, puedes usar useMemo para memorizar el resultado.

jsx
import React, { useState, useMemo } from 'react';

function ExpensiveCalculation() {
const [count, setCount] = useState(0);

// Memorizamos el cálculo costoso
const expensiveCalculation = useMemo(() => {
  console.log("Calculando...");
  return count * 2;
}, [count]); // Solo se recalcula cuando count cambia

return (
  <div>
    <p>Resultado costoso: {expensiveCalculation}</p>
    <button onClick={() => setCount(count + 1)}>Incrementar</button>
  </div>
);
}
  Explicación

Aquí, expensiveCalculation solo se recalcula cuando count cambia. Esto evita que el cálculo costoso se ejecute en cada renderizado.

2. Filtrar Datos de una Lista

Si tienes una lista de elementos y deseas filtrar esa lista basándote en algún criterio, puedes usar useMemo para evitar recalcular el filtro en cada render.

jsx
import React, { useState, useMemo } from 'react';

function FilteredList() {
const [filter, setFilter] = useState('');
const items = ['Venezuela', 'México', 'Colombia', 'Perú', 'Ecuador', 'Bolivia'];

// Memorizamos el resultado del filtro
const filteredItems = useMemo(() => {
  return items.filter(item => item.toLowerCase().includes(filter.toLowerCase()));
}, [filter]); // Solo se recalcula cuando el filtro cambia

return (
  <div>
    <input
      type="text"
      placeholder="Filtrar países"
      value={filter}
      onChange={(e) => setFilter(e.target.value)}
    />
    <ul>
      {filteredItems.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  </div>
);
}
  Explicación

filteredItems solo se recalcula cuando filter cambia, lo que optimiza el proceso de filtrado en grandes listas.

3. Filtrar una Lista con useMemo

Imagina que tienes una lista de tareas, y solo quieres mostrar aquellas que están marcadas como “completadas”. Usando **useMemo*, puedes memorizar el resultado del filtro para evitar hacer el mismo cálculo en cada render.

bash
import React, { useState, useMemo } from 'react';

function TaskList() {
const [showCompleted, setShowCompleted] = useState(true);
const tasks = [
  { id: 1, text: 'Comprar leche', completed: true },
  { id: 2, text: 'Estudiar React', completed: false },
  { id: 3, text: 'Hacer ejercicio', completed: true },
  { id: 4, text: 'Llamar a mamá', completed: false }
];

// Memorizamos las tareas completadas o no, dependiendo del filtro
const filteredTasks = useMemo(() => {
  return tasks.filter(task => task.completed === showCompleted);
}, [showCompleted, tasks]); // Se recalcula solo cuando showCompleted o tasks cambian

return (
  <div>
    <button onClick={() => setShowCompleted(!showCompleted)}>
      {showCompleted ? 'Ver Incompletas' : 'Ver Completas'}
    </button>
    <ul>
      {filteredTasks.map(task => (
        <li key={task.id}>{task.text}</li>
      ))}
    </ul>
  </div>
);
}

export default TaskList;
  Explicación

  • useMemo para optimización de filtrado:
    • useMemo se utiliza para memorizar el resultado de filteredTasks. Solo se recalcula cuando el estado showCompleted o la lista de tasks cambian.
    • Si no usas useMemo, el filtrado de la lista se realizaría en cada render, lo que podría ser ineficiente, especialmente con listas grandes.
  • Estado showCompleted:
    • Cuando haces clic en el botón, el estado showCompleted cambia entre true y false, lo que activa el filtrado de las tareas completadas o no.

4. Optimización de Componentes Hijo con Propiedades Complejas

Si un componente hijo recibe una propiedad compleja que cambia con frecuencia, puedes usar useMemo para memorizar esta propiedad y evitar renderizados innecesarios.

jsx
import React, { useState, useMemo } from 'react';

function ParentComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('Juan');

// Memorizamos el objeto de propiedades
const person = useMemo(() => ({ name, count }), [name, count]);

return (
  <div>
    <ChildComponent person={person} />
    <button onClick={() => setCount(count + 1)}>Incrementar</button>
    <button onClick={() => setName('Carlos')}>Cambiar Nombre</button>
  </div>
);
}

function ChildComponent({ person }) {
console.log('Renderizando Child');
return <p>{person.name} tiene {person.count} años.</p>;
}
  Explicación

Usando useMemo, el objeto person se memoriza y solo cambia cuando name o count cambian, evitando que el componente hijo se renderice innecesariamente si los valores no han cambiado.

5. Evitar Cálculos en un Hook con Dependencias

Si estás realizando un cálculo que depende de varias dependencias, puedes usar useMemo para evitar que se vuelva a calcular innecesariamente.

jsx
import React, { useState, useMemo } from 'react';

function PrimeNumberChecker() {
const [number, setNumber] = useState(1);

// Memorizamos el resultado de si el número es primo
const isPrime = useMemo(() => {
  let isPrime = true;
  for (let i = 2; i < number; i++) {
    if (number % i === 0) {
      isPrime = false;
      break;
    }
  }
  return isPrime;
}, [number]); // Solo se recalcula cuando number cambia

return (
  <div>
    <p>{number} es primo: {isPrime ? 'Sí' : 'No'}</p>
    <button onClick={() => setNumber(number + 1)}>Incrementar Número</button>
  </div>
);
}
  Explicación

Aquí, el cálculo de si un número es primo se memoriza, evitando que se vuelva a realizar en cada render.

6. Optimización de un Componente de Lista con Paginación

Si estás renderizando una lista grande y la paginación está controlada por el estado, puedes usar useMemo para optimizar la recalculación de los elementos que se deben mostrar en cada página.

jsx
import React, { useState, useMemo } from 'react';

function PaginatedList() {
const [page, setPage] = useState(1);
const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6', 'Item 7', 'Item 8'];

const itemsPerPage = 3;

// Memorizamos los elementos que deben mostrarse en la página actual
const paginatedItems = useMemo(() => {
  const startIndex = (page - 1) * itemsPerPage;
  return items.slice(startIndex, startIndex + itemsPerPage);
}, [page, items]);

return (
  <div>
    <ul>
      {paginatedItems.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
    <button onClick={() => setPage(page - 1)} disabled={page === 1}>Anterior</button>
    <button onClick={() => setPage(page + 1)} disabled={page * itemsPerPage >= items.length}>Siguiente</button>
  </div>
);
}
  Explicación

paginatedItems se memoriza para que solo se recalcule cuando cambie la página o los elementos, mejorando la eficiencia al manejar listas grandes con paginación.

7. Objetos como Props

Usar useMemo para optimizar el estilo de un componente según una propiedad (prop):

jsx
import React, { useState, useMemo } from 'react';

function Button() {
const [isActive, setIsActive] = useState(false);

// Memoriza el estilo del botón según el estado
const buttonStyle = useMemo(() => ({
  backgroundColor: isActive ? 'green' : 'gray',
  color: 'white',
  padding: '10px 20px',
  border: 'none',
  borderRadius: '5px',
  cursor: 'pointer',
}), [isActive]); // Solo recalcula cuando isActive cambia

return (
  <button 
    style={buttonStyle}
    onClick={() => setIsActive(!isActive)}
  >
    {isActive ? 'Activo' : 'Inactivo'}
  </button>
);
}

export default Button;
  Explicación

¿Qué hace useMemo aquí?

  • useMemo memoriza el estilo del botón para evitar que se recalculen los estilos en cada renderizado, solo cuando el estado isActive cambie.
  • El estilo del botón depende de si isActive es true o false. Si el estado cambia, useMemo recalcula el estilo; de lo contrario, usa el valor previamente calculado.

8. Memorización Condicional con useMemo

En este ejemplo, usamos useMemo para optimizar el cálculo de la suma de números mayores a un valor específico (el umbral). Si el valor del umbral no cambia, la suma no se recalcula, lo que mejora el rendimiento evitando cálculos innecesarios en cada render.

jsx
import React, { useState, useMemo } from 'react';

function SumaCondicional() {
const [umbral, setUmbral] = useState(10); // Valor umbral
const numeros = [5, 12, 8, 20, 15, 3, 7]; // Lista de números

// Memorizamos la suma solo si el umbral cambia
const sumaNumeros = useMemo(() => {
  return numeros.filter(num => num > umbral).reduce((acc, num) => acc + num, 0);
}, [umbral]); // Solo recalcular si cambia el umbral

return (
  <div>
    <h2>Suma de números mayores a {umbral}: {sumaNumeros}</h2>
    <button onClick={() => setUmbral(umbral + 5)}>
      Aumentar umbral
    </button>
  </div>
);
}

export default SumaCondicional;
  Explicación

  • Uso de useMemo: La suma de los números solo se recalcula si el valor de umbral cambia. Si no cambia, se usa el valor previamente calculado, evitando cómputos innecesarios.
  • Filtrar y Reducir: Primero, se filtran los números que son mayores que el umbral, luego se suma solo esos valores.
  • Optimización: useMemo optimiza el rendimiento al recalcular la suma solo cuando es necesario, evitando recalcular en cada renderizado.

9. Optimización de Dependencias

Este ejemplo demuestra cómo optimizar las dependencias de useMemo para evitar recalculaciones innecesarias. En React, cuando se pasan demasiadas dependencias a useMemo, el hook se recalcula cada vez que alguna de ellas cambia, lo cual puede afectar el rendimiento.

jsx
import React, { useState, useMemo } from 'react';

function Calculadora() {
const [numero1, setNumero1] = useState(0);
const [numero2, setNumero2] = useState(0);

// ❌ Mala práctica: muchas dependencias
const suma = useMemo(() => numero1 + numero2, [numero1, numero2]);

// ✅ Buena práctica: agrupar datos en un solo objeto
const numeros = { numero1, numero2 };
const sumaOptima = useMemo(() => numeros.numero1 + numeros.numero2, [numeros]);

return (
  <div>
    <h2>Suma: {sumaOptima}</h2>
    <button onClick={() => setNumero1(numero1 + 1)}>Incrementar Número 1</button>
    <button onClick={() => setNumero2(numero2 + 1)}>Incrementar Número 2</button>
  </div>
);
}

export default Calculadora;

10. Manejo de Objetos y Arrays

Este ejemplo muestra cómo usar useMemo de manera efectiva para evitar recalcular objetos y arrays innecesariamente en React.

jsx
// ✅ Buena práctica con objetos
const memoizedObject = useMemo(() => ({
name: user.name,
age: user.age
}), [user.name, user.age]);

// ✅ Buena práctica con arrays
const memoizedArray = useMemo(() => 
items.map(item => transformItem(item)),
[items]
);
  Conclusiones

Usar useMemo en React es una excelente manera de optimizar el rendimiento cuando tienes cálculos o transformaciones costosas que no necesitan ser recalculadas en cada renderizado. Recuerda:

  • Úsalo solo cuando sea necesario
  • Mide el impacto en el rendimiento
  • Mantén las dependencias al mínimo
  • Considera el costo de la memorización vs. el recálculo

Documentación Oficial 📚