Utilizando los Hooks useCallback y useMemo

Utilizando los Hooks useCallback y useMemo

¿Cómo están viajeros y viajeras web? Dando continuidad a este curso de gratuito de React.JS, veremos algunos Hooks con los cuales podremos mejorar el rendimiento de nuestra aplicación, aprenderemos a utilizar useCallback y useMemo y, espero te sea de utilidad, pero, como vimos en el post anterior debes ser precavido al momento de decidir utilizar o no este tipos de Hooks.

  • Antes de continuar te recomiendo visitar el post done hablamos de React.memo(), ya que, en este post te dejo recomendaciones que deberías tomar en cuenta antes de memorizar tus componentes.

 

📌 Suscríbete a mi canal y activa la campanita para que no te pierdas ningún video 🤘

 

¿Qué aprenderás de useCallback y useMemo ?

  • ¿Qué es y para qué sirve useCallback?
  • Aprenderás a utilizar useCallback en conjunto con React.memo
  • ¿Qué es y para qué sirve useMemo?

 

 

Repositorio

  • Puedes descargar el código de de esta clase aquí.
  • O si deseas clona el repositorio y cambia a la rama "usecallback".

 

git clone https://github.com/EWebik/react-js.git
git checkout usecallback

 

¿Qué es y para qué sirve useCallback?

El Hook useCallback es un elemento de React que nos permite memorizar una función, por lo tanto, la lógica en su interior trabajará con los valores iniciales establecidos en el momento en que fue creada.

No obstante, como todo Hook, recibe como segundo argumento un arreglo de dependencias, con las cuales, le indicamos a React cuando es que la función debe actualizarse, veamos su declaración.

 

Declaración useCallback

const memoizedCallback = useCallback(
  () => {
    //Lógica que es afectada o afecta a las dependencias "a" y "b"
  },
  [a, b],
);

 

¿Cómo utilizar useCallback con React.memo()?

Bien, si con useCallback memorizamos una función, con React.memo lo que hacemos es memorizar un componente y evitar que este renderice cuando no sea necesario.

Si en nuestra aplicación evitamos que un componente renderice cuando no sea necesario, vamos a obtener como resultado que nuestras páginas tengan un mejor rendimiento y sean más rápidas.

No obstante, no siempre es necesario utilizar este tipo de elementos si quieres saber cuando usar y cuando no usar memorización, revisa el post de React.memo.

Antes de utilizar useCallback debemos memorizar nuestro componente, así que voy a crear un componente que renderizará una lista y posteriormente lo envolveré con el HOC React.memo.

 

Componente sin memorizar

const List = (props)=>{
  const{list,handleDelete}=props;
  return(
    <div className="hijo">
      <h2>{"Lista memorizada"}</h2>
      <p>{"Último render: " + new Date().getMilliseconds()}</p>
      {
        list.map((item)=>(
          <div key={item.id}>
            <span style={{marginRight:10}}>
              {`Id: ${item.id}, Nombre: ${item.nombre}`}
            </span>
            <button onClick={()=>{
              handleDelete(item.id)
            }}>{"Eliminar"}</button>
          </div>
        ))
      }
    </div>
  )
}
  • El componente List, recibe dos propiedades:
    • List: arreglo de datos
    • HandleDelete: función que nos permite eliminar un dato o item del arreglo
  • El objetivo principal de este componente es crear una lista con los datos que vienen en la propiedad List.
  • Cada item o dato renderizado mostrará un botón que nos permitirá eliminar un item en particular.
  • Y vamos a tener un texto con la leyenda "Ultimo render" donde imprimimos new Date().getMilliseconds() para saber si nuestro componente ha vuelto a renderizar.

 

Salida HTML del componente List

 

Memorizando el componente List con React.memo

const ListMemo = React.memo(
  (props)=>{
    return(
      <List {...props} />
    )
  }
)
  • Para controlar el renderizado de nuestra lista, debemos envolver a nuestro componente con el HOC React.memo, tal como lo hacemos con el nuevo componente; ListMemo.
  • El componente ListMemo, solo volverá a renderizarse cuando sus propiedades cambien, no obstante, recuerda que el componente List, recibe la función handleDelete, la cual debes manejar cuidadosamente para que no interfiera con tus reglas de renderizado, esto lo veremos a continuación.

Hasta aquí no tenemos nada nuevo, hemos memorizado a List con React.memo, ahora solo hay que mandarla llamar en nuestro componente principal.

const Component = ()=>{
  const [counter1, setCounter1] = useState(0);
  const [list, setList] = useState([]);
  const [id, setId] = useState(0);
  const [update, setUpdate] = useState(0);

  useEffect(()=>{
    let interval = setInterval(() => {
      setCounter1(counter1 + 1);
      if(counter1 >= 100){
        let tList = list;
        tList.push({
          id:id,
          nombre:"Producto"+id
        });
        setList(tList);
        setId(id + 1);
        setCounter1(0);
        setUpdate(!update);
      }
    }, 1);
    return ()=>{
      clearInterval(interval);
    }
  },[counter1]);

  const handleDeleteCallback = 
    (id)=>{
      const tList = list.filter((item)=>{
        return item.id !== id
      });
      setList(tList);
      setUpdate(!update);
    }

  return(
    <div className="padre">
      <h1>{"EWebik mejorando el rendimiento useCallback y useMemo"}</h1>
      <div className="hijos">
        <Counter
          title={"No memorizado"}
          counter={counter1}/>
      </div>
      <div className="hijos">
      <ListMemo
        list={list}
        update={update}
        handleDelete={handleDeleteCallback} />
      </div>
     
    </div>
  )
}
  • Como puedes observar, en nuestro componente principal mandamos a renderizar ListMemo, el cual es nuestro componente memorizado.
  • ListMemo recibe tres propiedades:
    • list: arreglo de datos de la lista
    • update: lo utilizamos para forzar el renderizado
    • handleDelete: función que nos permite eliminar un item del arreglo
  • Dentro del cuerpo de la función, tenemos un useEffect, el cual es un Hook de efecto que nos permite realizar tareas; por ejemplo, en este caso, tenemos un setInterval que nos permite ejecutar una acción o tarea cada determinado tiempo, en este caso se ejecutara cada milisegundo e incrementará el valor de la variable de estado counter1.
  • Dentro de la función de intervalo tenemos una condicional "if" con la cual cada 1000 milisegundos vamos insertar un nuevo dato a la variable de estado list, con lo cual, afectaremos nuestro componente ListMemo agregando un nuevo dato a nuestra lista.

Hemos llegado a un punto muy importante, tal y como esta el código anterior, cada determinado tiempo se agregará un nuevo dato a la lista y, si ListMemo esta memorizado, el objetivo sería que solo volviera a renderizar cuando el nuevo dato sea agregado a la lista.

Lamentablemente tal como esta el código anterior, este objetivo no se cumple, si bien:

  • El componte ListMemo se vuelve a renderizar cuando un dato es agregado a la lista.
  • También se renderiza por cada incremente que tiene counter1, debido al proceso que esta corriente dentro de useEffect.
  • Y esto ocurre, ya que, al cambiar el valor de counter1, el componente principal se actualiza y vuelve a crear todo lo que esta dentro del cuerpo de la función y renderiza todo lo que regresa desde su return.
  • Entonces, si vuelve a crear todo lo que hay dentro del cuerpo de la función, también volvería a crear la función handleDeleteCallback, lo que causaría que React.memo lo tome como un cambio en las propiedades y vuelva a renderizar el componente.

Y gracias a este comportamiento nuestro memorización a través de React.memo, no sirve para nada 😭, pero, hay una forma de solucionarlo y es utilizando el Hook useCallback, con este Hook vamos a memorizar la función handleDeleteCallback, para que, aunque el componente principal vuelva a renderizarse, esta función no se vuelva a crear.

const handleDeleteCallback = useCallback(
    (id)=>{
      const tList = list.filter((item)=>{
        return item.id !== id
      });
      setList(tList);
      setUpdate(!update);
    },[]);
  • useCallback memoriza la creación de handleDeleteCallback, y todo lo que este dentro de ella, esto quiere decir, que cada vez que se ejecute tomará los datos y valores que tenían las variables list y update, al momento de ser creada.
  • Si no pasamos nada en el array de dependencias, la función se creará una sola vez cuando el componente principal es montado y no volverá a actualizarse.
  • Si la función no vuelve a actualizarse, tendríamos efectos negativos en algunos casos, ya que la variable list, ira creciendo, pero la función handleDeleteCallback no lo detectará y cada vez que la invoquemos, veremos cosas raras, te recomiendo ver el vídeo ya que hay ejemplifico claramente esta situación.
  • Para que la función se ejecute correctamente, pasamos list o update al arreglo de dependencias, y cada vez que alguna de estas variables cambie, la función se volverá a crear y ListMemo volverá a renderizar.
  const handleDeleteCallback = useCallback(
    (id)=>{
      const tList = list.filter((item)=>{
        return item.id !== id
      });
      setList(tList);
      setUpdate(!update);
    },[list]);

Excelente, con todos estos cambios integrando useCallback, ya tendríamos al componente ListMemo memorizado y solo se volverá a renderizar, cuando sea necesario.

Solo ten en cuenta los consejos que te mencione en la clase de React.memo, ya que muchas veces el memorizar componentes no es la primera solución en la que deberías de pensar.

Bien, ahora pasemos a revisar el Hook useMemo, con el cual podremos memorizar componentes, solo que lo haremos desde el cuerpo de la función de nuestro componente principal.

 

¿Qué es y para que sirve useMemo?

El Hook useMemo nos permite memorizar un componente de React, es similar al HOC React.memo, ya que, mucho de lo que se hace con React.memo, lo podemos hacer con este Hook.

 

Declaración useMemo

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

Como todo Hook, en el primer argumento recibe una función y como segundo argumento recibirá las dependencias, si recuerdas, con React.memo controlamos que vulva a renderizar siempre y cuando haya un cambio en sus propiedades.

Con useMemo vamos a controlar de una forma similar, pero lo haremos a través del array de dependencias.

 

¿Cómo utilizar useMemo?

Para ejemplificar el uso de este Hook vamos a retomar el componente List que vimos en la primera sección de este post y, lo que vamos hacer es memorizarlo a través de este Hook.

 const listUseMemo = useMemo(
    ()=>
    <List
      list={list}
      update={update}
      handleDelete={handleDelete} />
  ,[]);
  • Como puedes observar, dentro del primer argumento agregamos una función que regresa un componente, en este caso, List, no obstante, es un componente memorizado.
  • Si nosotros no agregamos nada en el arreglo de dependencias, useMemo regresará el componente memorizado en el momento en que componente principal es montado y no volverá a actualizar, en nuestro caso, regresará siempre una lista vacía.
  • Entonces, en la lista de dependencias agregaremos la variable de estado update, la cual cambia cada vez que se agrega un nuevo elemento a la lista, con esto, le indicamos a useMemo que debe retornar un nuevo componente memorizado, pero con las propiedades actualizadas.
 const listUseMemo = useMemo(
    ()=>
    <List
      list={list}
      update={update}
      handleDelete={handleDelete} />
  ,[update]);

 

Otro cambio importante al utilizar useMemo, es que en muchas ocasiones no es necesario utilizar useCallback, ya que, como puedes ver en el código anterior, no estamos utilizando handleDeleteCallback, sino simplemente handleDelete, la cual es una función no memorizada.

const handleDelete =
      (id)=>{
        const tList = list.filter((item)=>{
          return item.id !== id
        });
        setList(tList);
        setUpdate(!update);
      };

Y esto se debe a que el Hook useMemo toma todos los parámetros necesarios una sola vez, y regresa el componente memorizado, y solo actualiza, cuando hay un cambio en su arreglo de dependencias, no en las propiedades.

Aquí te dejo el código completo para useMemo

const Component = ()=>{
  const [counter1, setCounter1] = useState(0);
  const [list, setList] = useState([]);
  const [id, setId] = useState(0);
  const [update, setUpdate] = useState(0);

  useEffect(()=>{
    let interval = setInterval(() => {
      setCounter1(counter1 + 1);
      if(counter1 >= 1000){
        let tList = list;
        tList.push({
          id:id,
          nombre:"Producto"+id
        });
        setList(tList);
        setId(id + 1);
        setCounter1(0);
        setUpdate(!update);
      }
    }, 1);
    return ()=>{
      clearInterval(interval);
    }
  },[counter1]);

    const handleDelete =
      (id)=>{
        const tList = list.filter((item)=>{
          return item.id !== id
        });
        setList(tList);
        setUpdate(!update);
      };

  //useMemo
  const listUseMemo = useMemo(
    ()=>
    <List
      list={list}
      update={update}
      handleDelete={handleDelete} />
  ,[update]);

  return(
    <div className="padre">
      <h1>{"EWebik mejorando el rendimiento useCallback y useMemo"}</h1>
      <div className="hijos">
        <Counter
          title={"No memorizado"}
          counter={counter1}/>
      </div>
      <div className="hijos">
        {
          listUseMemo
        }
      </div>
    </div>
  )
}

 

Si eres observador, quizá te hayas dado cuenta que para agregar en nuestro marcado la variable que proviene de useMemo "listUseMemo", no lo hacemos como componente, sino como una expresión Javascript.

<div className="hijos">
        {
          listUseMemo
        }
</div>

 

Diferencia entre useMemo y React.memo

Con todo lo que hemos visto en este post, podemos establecer la principal diferencia entre React.memo y useMemo:

  • La principal diferencia es que React.memo actualiza siempre y cuando haya un cambio en sus propiedades, mientras que, useMemo, solo actualiza si hay un cambio en el array de dependencias.
  • Otra diferencia es que React.memo es un HOC y useMemo es un Hook.

 

Muy bien, espero haberme podido explicar claramente, te recomiendo ver el vídeo de esta clase para que veas en acción todo lo que te he explicado, recuerda suscribirte al boletín y al canal en YouTube para que te enteres cuando suba las siguientes clases, nos vemos en el siguiente post.

 

 


Juan Carlos G

Electrónica y diseño web

Durante años he desarrollado plataformas dedicadas al rastreo satelital y varios sitios web que se encuentran en la primera página de Google, y hoy quiero compartir contigo lo que se en tecnologías como Node JS, PHP, C# y Bases de datos, si quieres apoyarme sígueme en mis redes sociales y suscríbete a mi canal de YouTube.

Puedes seguirme en mis redes