El hook useCallback y React.memo

Descubre lo bien que trabajan juntos este hooks y la utilidad React.memo para memorizar componentes

Gerardo Fernández
4 min readDec 13, 2020

Hace unos meses escribí un artículo en donde hablaba de los hooks useCallback y useMemo que nos permiten optimizar el rendimiento de nuestra aplicación:

Hoy quiero escribir un artículo complementario en donde veamos una de las aplicaciones más interesantes que tiene el hook useCallback , ya que creo que de este modo resulta mucho más fácil encontrarle utilidad y animarnos a usarlo más a menudo.

¡Comencemos!

El hook useCallback

Antes de comenzar recordemos cómo funciona el hook useCallback . Este “hook” nos permite memorizar la función que le pasemos como argumento, devolviéndonos siempre la misma “instancia” render tras render hasta que cambie alguna de las dependencias que especifiquemos.

Las dependencias del “hook” useCallback funcionan del mismo modo que las del hook useEffect : son especificadas por medio de un array que pasamos como segundo argumento al hook. En el momento en que alguna de ellas cambie recibiremos una nueva versión de la función.

import {useCallback} from 'react';function Component() {

// ....
const onClick = useCallback(() => console.log('Click'); []);
return (<div>
<MyButton onClick={onClick}>Púlsame</MyButton>
...
);
}

Su uso suele estar muy asociado a la creación de “custom hooks” ya que es una buena práctica devolver memorizadas las funciones que en ellos creemos para que puedan ser empleadas dentro de otros hooks (como el hook useEffect ) sin miedo a que causen problemas al emplearlas como dependencias.

import {useCallback, useState} from 'react';

export default function useCount(initialValue = 0) {
const [count, setCount] = useState(initialValue);

const increment = useCallback(function() {
setCount(count => count + 1);
}, [setCount]);


return [count, increment];
}

Gracias a este uso del hook useCallback podemos emplear la función increment dentro de un efecto sin miedo a declararlo en las dependencias, ya que siempre obtendremos la misma función:

useEffect(function() {
// something
console.log(otherDep);
increment();
}, [increment, otherDep]);

Sin embargo, tiene otro uso muy interesante como estamos a punto de ver.

React.memo

La función React.memo nos permite memorizar un componente. Esto permite a React evitar renderizarlo si sus propiedades no han cambiado, con la consiguiente optimización en lo que a rendimiento se refiere.

import React from 'react';function Footer({user}) {
return (...);
}
export default React.memo(Footer);

En este caso, cuando empleemos el componente Footer en nuestra aplicación, React evitará renderizarlo de nuevo siempre y cuando su propiedad user no cambie.

🤓 Quizás ya adivines lo que estoy a punto de contarte.

Combinando useCallback y React.memo

Para entender lo que estoy a punto de contarte es importante que recuerdes que las funciones que declaramos en nuestros componentes se crean cada vez que el componente se renderiza.

Esto quiere decir lo siguiente:

// 1️⃣ MyButton.jsfunction MyButton({onClick}) {
return <button onClick={onClick}>Pulsar</button>
}
export default React.memo(MyButton);// 2️⃣ Component.jsfunction Component() {

// ....
function onClick() {
...
}
return (<div>
<MyButton onClick={onClick}>Púlsame</MyButton>
...
);
}

Cada vez que el componente Component se renderiza estamos creando una nueva función handleClick y por tanto, MyButton está recibiendo en cada render una nueva función handleClick .

Esto significa que memorizar el componente MyButton por medio de React.memo no tendría efecto ya que la propiedad onClick del render “N” sería distinta de la del render “N+1”.

De una forma simplificada puedes pensarlo de este modo:

function createFn() {
return function foo() { console.log('Hola'); }
}
const fn1 = createFn();
const fn2 = createFn();
console.log(fn1 === fn2); // false

Sin embargo, gracias al hook useCallback podemos memorizar las funciones para obtener en cada render la misma “instancia” siempre y cuando las dependencias no cambien.

// 1️⃣ MyButton.jsfunction MyButton({onClick}) {
return <button onClick={onClick}>Pulsar</button>
}
export default React.memo(MyButton);// 2️⃣ Component.jsfunction Component() {

// ....
const onClick = useCallback(() => console.log('Click'); []);
return (<div>
<MyButton onClick={onClick}>Púlsame</MyButton>
...
);
}

Como en este caso el array de dependencias del hook useCallback se encuentra vacío, render tras render obtendremos la misma función onClick , lo cual permitirá a React aprovechar la memorización del componente MyButton y renderizarlo una única vez.

Ejemplo

Como al principio estas cosas son difíciles de ver he creado el siguiente CodeSandbox.

En él estoy definiendo un hook useCount que devuelve una función increment y su correspondiente versión memorizada.

El componente AppView del proyecto pinta un botón que al pulsar invoca a la función que recibe como propiedad. Este componente AppView se encuentra memorizado gracias a React.memo .

El componente AppView lo uso dos veces:

  • Una pasándole la versión memorizada de la función increment .
  • Otra pasándole la versión sin memorizar.

Al pulsar los botones podrás ver como el AppView que emplea la versión memorizada de increment no vuelve a renderizarse, algo que sí sucede con el componente AppView que usa la versión sin memorizar.

Conclusiones

Como has podido ver, el hook useCallback nos mete de lleno en un terreno muy interesante como es la optimización de los renders que React realiza, algo que suele ser de vital importancia en aplicaciones de gran tamaño.

Emplearlo en conjunción con la utilidad React.memo nos puede ahorrar numerosos renders de componentes pesados (por ejemplo la cabecera y el footer), algo que terminaremos agradeciendo más pronto que tarde.

¿Quieres recibir más artículos como este?

Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇

--

--

Gerardo Fernández

Entre paseo y paseo con Simba desarrollo en Symfony y React