Explorando useMemo, useCallback y useLayoutEffect en React

React nos ha proporcionado herramientas poderosas para desarrollar aplicaciones web interactivas y eficientes. Con la introducción de los hooks, se abrió un mundo de posibilidades para manejar estados, efectos y funciones de manera más declarativa. Más allá de los hooks básicos (useState, useEffect), existen otros más avanzados como useMemo, useCallback y useLayoutEffect, diseñados para abordar desafíos específicos relacionados con el rendimiento y la experiencia del usuario.

En este artículo, exploraremos estos tres hooks en profundidad, desde sus fundamentos teóricos hasta su implementación práctica en una aplicación React que simula un ecommerce.


1. useMemo: Optimizando cálculos innecesarios

¿Qué es?

useMemo es un hook que nos permite memorizar el resultado de una función costosa para evitar que se recalculen innecesariamente en cada renderizado. Es especialmente útil cuando trabajamos con grandes conjuntos de datos o funciones intensivas en computación.

¿Cómo funciona?

Cuando React renderiza un componente, todas las funciones dentro del componente se ejecutan. Si una función realiza cálculos pesados, esto puede afectar significativamente el rendimiento. Aquí es donde entra useMemo, que guarda ("memoriza") el resultado de una función y solo la vuelve a ejecutar si alguna de las dependencias cambia.

Características clave:

  • Memoriza valores computados.
  • Se usa principalmente para optimizar componentes que dependen de cálculos intensivos.
  • Acepta dos argumentos: una función y un arreglo de dependencias.

Ejemplo básico:

const memoizedValue = useMemo(() => { 
	return computeExpensiveValue(a, b); 
}, [a, b]);

En este caso, computeExpensiveValue solo se recalculará cuando a o b cambien.


2. useCallback: Memoriza funciones

¿Qué es?

useCallback se utiliza para memorizar funciones, evitando que React cree nuevas instancias de la función en cada renderizado. Esto es útil cuando pasamos funciones como props a componentes hijos, ya que previene que estos se re-rendericen innecesariamente.

¿Por qué es importante?

En React, cuando un componente padre se vuelve a renderizar, React considera que cualquier función definida dentro de ese componente es "nueva". Esto puede llevar a que los componentes hijos que reciben esa función como prop también se re-rendericen, incluso si no es necesario.

Características clave:

  • Memoriza la referencia de una función.
  • Es útil en combinación con React.memo, que optimiza componentes para no renderizarse si sus props no cambian.
  • Acepta dos argumentos: una función y un arreglo de dependencias.

Ejemplo básico:

const memoizedCallback = useCallback(() => { 
	doSomething(a, b); 
}, [a, b]);

Aquí, la función doSomething se volverá a definir únicamente si a o b cambian.


3. useLayoutEffect: Ajustes visuales antes del renderizado

¿Qué es?

useLayoutEffect es similar a useEffect, pero se ejecuta de manera síncrona después de que React ha realizado todas las modificaciones al DOM y antes de que el navegador pinte la pantalla. Esto permite realizar ajustes visuales inmediatos, como calcular dimensiones o reposicionar elementos.

¿Cuándo usarlo?

Se utiliza en situaciones donde necesitas leer o escribir directamente en el DOM y esos cambios deben reflejarse antes de que el usuario vea algo en pantalla.

Características clave:

  • Se ejecuta antes del renderizado visual.
  • Es útil para medir dimensiones, realizar cálculos de posición o sincronizar animaciones.
  • Acepta dos argumentos: una función y un array de dependencias.

Ejemplo básico:

useLayoutEffect(() => { 
	const dimension = element.getBoundingClientRect();
    console.log(dimension); 
}, [element]);

Implementación Práctica

Para fortalecer el conocimiento, vamos a aplicar estos hooks en una aplicación React,  un ecommerce que hemos venido desarrollando. El objetivo es mejorar el rendimiento y la experiencia del usuario utilizando useMemo, useCallback y useLayoutEffect.

Contexto de la Aplicación:

  • Es un ecommerce que ya utiliza hooks como useState y useEffect.
  • Necesitamos agregar filtros y tooltips, los cuales pueden generar impacto en el performance.
  • Queremos optimizar cálculos, evitar renders innecesarios y ajustar elementos visuales dinámicamente.

1. Aplicación de useMemo: Filtros dinámicos

En el componente ProductFilters.jsx, utilizamos useMemo para calcular las categorías y el rango de precios:

const categories = useMemo(() => { 
	return [...new Set(products.map((product) => product.category))]; }, [products]); 
    
const priceRange = useMemo(() => { 
	const prices = products.map((product) => product.price); 
    return { min: Math.floor(Math.min(...prices)), 
    		 max: Math.ceil(Math.max(...prices)), 
    }; 
 }, [products]);

Esto asegura que las categorías y los precios solo se recalculen cuando products cambie, mejorando el rendimiento en aplicaciones con grandes cantidades de datos.


2. Aplicación de useCallback: Optimización de funciones

En Home.jsx, usamos useCallback para manejar cambios en los filtros:

const handleCategoryChange = useCallback((category) => { 
	setCategory(category); 
}, []); 

const handlePriceChange = useCallback((price) => { 
	setMaxPrice(price); 
}, []);

Esto evita que las funciones sean recreadas en cada renderizado, reduciendo renders innecesarios en el componente ProductFilters.


3. Aplicación de useLayoutEffect: Tooltips ajustables

En el componente PriceTooltip.jsx, utilizamos useLayoutEffect para ajustar la posición de un tooltip dentro del viewport:

useLayoutEffect(() => { 
	if (showTooltip && tooltipRef.current && containerRef.current) { 
    	const containerRect = containerRef.current.getBoundingClientRect(); 
        const tooltipRect = tooltipRef.current.getBoundingClientRect(); 
        const newPosition = { 
        	top: -tooltipRect.height, 
        	left: containerRect.left + tooltipRect.width > window.innerWidth ? -(tooltipRect.width - containerRect.width) : 0, 
        }; 
       	setTooltipPosition(newPosition); 
    } 
}, [showTooltip]);

Esto asegura que los tooltips sean visibles y estén correctamente posicionados en todo momento.


Al finalizar la implementación de filtros y mejoras visuales, usando la combinación de useMemo, useCallback y useLayoutEffect, logramos mejorar el perfomance de nuestra aplicación y en general estos hooks permiten crear aplicaciones React más rápidas, eficientes y visualmente atractivas.

Estos hooks avanzados no solo mejoran el rendimiento, sino que también optimizan la experiencia del usuario, llevándote un paso más allá en el desarrollo frontend. ¡Ponlos en práctica y observa cómo tus aplicaciones alcanzan un nuevo nivel!