useMemo es un Hook de React que te permite guardar en caché el resultado de un cálculo entre renderizados.

const cachedValue = useMemo(calculateValue, dependencies)

Referencia

useMemo(calcularValor, dependencias)

Llama a useMemo en el nivel superior de tu componente para guardar en caché un cálculo entre rerenderizados:

import { useMemo } from 'react';

function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}

Mira m√°s ejemplos debajo.

Par√°metros

  • calcularValor: La funci√≥n que calcula el valor que deseas memoizar. Debe ser pura, no debe aceptar argumentos y debe devolver un valor de cualquier tipo. React llamar√° a tu funci√≥n durante el renderizado inicial. En renderizados posteriores, React devolver√° el mismo valor nuevamente si las dependencias no han cambiado desde el √ļltima renderizado. De lo contrario, llamar√° a calcularValor, devolver√° su resultado y lo almacenar√° en caso de que pueda reutilizarse m√°s tarde.

  • dependencias: La lista de todos los valores reactivos a los que se hace referencia dentro del c√≥digo calcularValor. Los valores reactivos incluyen props, estado y todas las variables y funciones declaradas directamente dentro del cuerpo de tu componente. Si tu linter est√° configurado para React, verificar√° que cada valor reactivo est√© correctamente especificado como una dependencia. La lista de dependencias debe tener un n√ļmero constante de elementos y escribirse en l√≠nea como [dep1, dep2, dep3]. React comparar√° cada dependencia con su valor anterior usando el algoritmo de comparaci√≥n Object.is.

Devuelve

En el renderizado inicial, useMemo devuelve el resultado de llamar a calcularValor sin argumentos.

Durante los renderizados posteriores, devolver√° un valor ya almacenado del √ļltimo renderizado (si las dependencias no han cambiado), o llamar√° a calcularValor nuevamente y devolver√° el resultado que calcularValor haya devuelto.

Advertencias

  • useMemo es un Hook, por lo que solo puede llamarse en el nivel superior del componente o sus propios Hooks. No puedes llamarlo dentro de bucles o condiciones. Si lo necesitas, extrae un nuevo componente y mueve el estado a √©l.
  • En modo estricto, React llamar√° a tu funci√≥n de c√°lculo dos veces para ayudarte a encontrar impurezas accidentales. Este comportamiento ocurre solo en desarrollo y no afecta a producci√≥n. Si tu funci√≥n de c√°lculo es pura (como deber√≠a ser), esto no deber√≠a afectar la l√≥gica de tu componente. Se ignorar√° el resultado de una de las llamadas.
  • React no descartar√° el valor almacenado en cach√© a menos que haya una raz√≥n espec√≠fica para hacerlo. Por ejemplo, en desarrollo, React descartar√° la cach√© cuando edites el archivo de tu componente. Tanto en desarrollo como en producci√≥n, React desechar√° la cach√© si tu componente se suspende durante el montaje inicial. En el futuro, React puede agregar m√°s funcionalidades que hagan descartar la cach√©; por ejemplo, si React incorporara una funcionalidad para listas virtualizadas en el futuro, tendr√≠a sentido desechar la cach√© para los elementos que al desplazarse salen del √°rea visible de la lista virtualizada. Esto deber√≠a estar a tono con tus expectativas si dependes de useMemo √ļnicamente como una optimizaci√≥n de rendimiento. De lo contrario, una variable de estado o una ref pueden ser m√°s apropiadas.

Nota

Guardar en caché valores como este también se conoce como memoización,, y es por eso que el Hook se llama useMemo.


Uso

Evitar rec√°lculos costosos

Para almacenar en caché un cálculo entre renderizados, envuélvelo en useMemo en el nivel superior de tu componente:

import { useMemo } from 'react';

function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}

Necesitas pasar dos cosas a useMemo:

  1. Una función de cálculo que no toma argumentos, como () =>, y devuelve lo que querías calcular.
  2. Una lista de dependencias que incluye cada valor dentro de tu componente que se usa dentro del c√°lculo.

En el renderizado inicial, elvalor que obtendr√°s de useMemo ser√° el resultado de llamar a tu c√°lculo.

En cada procesamiento posterior, React comparar√° las dependencias con las dependencias que pasaste durante el √ļltimo renderizado. Si ninguna de las dependencias ha cambiado (comparado con Object.is), useMemo devolver√° el valor que ya calcul√≥ antes. De lo contrario, React volver√° a ejecutar tu c√°lculo y devolver√° el nuevo valor.

En otras palabras, useMemo guarda en caché un cálculo entre renderizados hasta que cambian sus dependencias.

Veamos un ejemplo para ver cu√°ndo es √ļtil.

De forma predeterminada, React volverá a ejecutar todo el cuerpo de tu componente cada vez que se vuelva a renderizar. Por ejemplo, si esta TodoList actualiza su estado o recibe nuevas props de su padre, la función filterTodos se volverá a ejecutar:

function TodoList({ todos, tab, theme }) {
const visibleTodos = filterTodos(todos, tab);
// ...
}

Por lo general, esto no es un problema porque la mayor√≠a de los c√°lculos son muy r√°pidos. Sin embargo, si est√°s filtrando o transformando un array grande, o est√°s realizando alg√ļn c√°lculo costoso, es posible que desees omitir hacerlo nuevamente si los datos no han cambiado. Si tanto todos como tab son los mismos que durante el √ļltimo renderizado, envolver el c√°lculo en useMemo como antes te permite reutilizar visibleTodos que ya se calcul√≥ antes. Este tipo de almacenamiento en cach√© se denomina memoizaci√≥n.

Nota

Solo debes depender de useMemo como una optimización de rendimiento. Si tu código no funciona sin él, encuentra el problema subyacente y arréglalo primero. Luego puedes agregar useMemo para mejorar el rendimiento.

Deep Dive

¬ŅC√≥mo saber si un c√°lculo es costoso?

En general, a menos que estés creando o recorriendo miles de objetos, probablemente no sea costoso. Si deseas tener más confianza, puede agregar un registro de consola para medir el tiempo dedicado a una pieza de código:

console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');

Realiza la interacci√≥n que est√°s midiendo (por ejemplo, escribiendo en la entrada de texto). Luego ver√°s registros como filter array: 0.15ms en tu consola. Si el tiempo total registrado suma una cantidad significativa (por ejemplo, ‚Äė1 ms‚Äô o m√°s), podr√≠a tener sentido memoizar ese c√°lculo. Como experimento, puedes envolver el c√°lculo en useMemo para verificar si el tiempo total registrado ha disminuido para esa interacci√≥n o no:

console.time('filter array');
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab); // Se omite si `todos` y `tab` no han cambiado.
}, [todos, tab]);
console.timeEnd('filter array');

useMemo no har√° que el primer renderizado sea m√°s r√°pido. Solo te ayuda a omitir el trabajo innecesario en las actualizaciones.

Ten en cuenta que tu máquina probablemente sea más rápida que la de tus usuarios, por lo que es una buena idea probar el rendimiento con una ralentización artificial. Por ejemplo, Chrome ofrece para esto una opción de CPU Throttling.

También ten en cuenta que medir el rendimiento en desarrollo no te dará los resultados más precisos. (Por ejemplo, cuando el Modo estricto está activado, verás que cada componente se renderiza dos veces en lugar de una vez). Para obtener los tiempos más precisos, construye tu aplicación para producción y pruébala en un dispositivo como el que tienen tus usuarios.

Deep Dive

¬ŅDeber√≠as agregar useMemo en todas partes?

Si tu aplicaci√≥n es como este sitio y la mayor√≠a de las interacciones son bruscas (como reemplazar una p√°gina o una secci√≥n completa), la memoizaci√≥n generalmente no es necesaria. Por otro lado, si tu aplicaci√≥n se parece m√°s a un editor de dibujos y la mayor√≠a de las interacciones son granulares (como formas en movimiento), entonces la memoizaci√≥n podr√≠a resultarte muy √ļtil.

Optimizar con useMemo solo es valioso en algunos casos:

  • El c√°lculo que est√°s poniendo en useMemo es notablemente lento y tus dependencias rara vez cambian.
  • Lo pasas como prop a un componente envuelto en memo. Quieres omitir el rerenderizado si el valor no ha cambiado. La memoizaci√≥n permite que tu componente se vuelva a renderizar solo cuando las dependencias no son las mismas.
  • El valor que est√°s pasando se usa m√°s tarde como una dependencia de alg√ļn Hook. Por ejemplo, tal vez otro c√°lculo con useMemo depende de este. O tal vez dependas de este valor dentro de un useEffect.

No hay ning√ļn beneficio en envolver un c√°lculo en useMemo en otros casos. Tampoco hay un da√Īo significativo en hacerlo, por lo que algunos equipos optan por no pensar en casos individuales y memoizar tanto como sea posible. La desventaja de este enfoque es que el c√≥digo se vuelve menos legible. Adem√°s, no toda la memoizaci√≥n es efectiva: un solo valor que es ‚Äúsiempre nuevo‚ÄĚ es suficiente para interrumpir la memoizaci√≥n de un componente completo.

En la pr√°ctica, puedes hacer que muchas memoizaciones sean innecesarias siguiendo algunos principios:

  1. Cuando un componente envuelve visualmente otros componentes, déjalo aceptar JSX como hijos. De esta manera, cuando el componente contenedor actualice su propio estado, React sabe que sus hijos no necesitan volverse a renderizar.
  2. Prefiere el estado local y no eleves el estado m√°s all√° de lo necesario. Por ejemplo, no mantengas el estado transitorio ‚ÄĒcomo formularios y si se le est√° haciendo hover a un elemento‚ÄĒ en la parte superior de tu √°rbol o en una biblioteca de estado global.
  3. Mant√©n tu l√≥gica de renderizado pura. Si volver a renderizar un componente causa un problema o produce alg√ļn artefacto visual notable, ¬°es un error en tu componente! Soluciona el error en lugar de agregar memoizaci√≥n.
  4. Evita Efectos innecesarios que actualicen el estado. La mayoría de los problemas de rendimiento en las aplicaciones de React son causados por cadenas de actualizaciones que se originan en Efectos que hacen que tus componentes se rendericen una y otra vez.
  5. Intenta eliminar las dependencias innecesarias de tus Efectos. Por ejemplo, en lugar de memoizar, suele ser m√°s sencillo mover alg√ļn objeto o funci√≥n dentro de un Efecto o fuera del componente.

Si una interacci√≥n espec√≠fica a√ļn se siente lenta, usa el generador de perfiles de la Herramientas de Desarrollo de React para ver qu√© componentes se beneficiar√≠an m√°s de la memoizaci√≥n y agregar memoizaci√≥n donde sea necesario. Estos principios hacen que tus componentes sean m√°s f√°ciles de depurar y comprender, por lo que es bueno seguirlos en cualquier caso. A largo plazo, estamos investigando hacer memoizaci√≥n granular autom√°ticamente para solucionar esto de una vez por todas.

La diferencia entre useMemo y calcular un valor directamente

Ejemplo 1 de 2:
Saltarse el rec√°lculo con useMemo

En este ejemplo, la implementaci√≥n de filterTodos se ralentiza artificialmente para que puedas ver qu√© sucede cuando alguna funci√≥n de JavaScript que est√°s llamando durante el renderizado es realmente lenta. Intenta cambiar las pesta√Īas y alternar el tema.

Cambiar las pesta√Īas se siente lento porque obliga a que el filterTodos ralentizado se vuelva a ejecutar. Eso es de esperar porque tab ha cambiado, por lo que todo el c√°lculo necesita volver a ejecutarse. (Si tienes curiosidad por qu√© se ejecuta dos veces, se explica aqu√≠.)

A continuaci√≥n, intenta alternar el tema. ¬°Gracias a useMemo, es r√°pido a pesar de la ralentizaci√≥n artificial! La llamada lenta a filterTodos se omiti√≥ porque tanto todos como tab (que pasas como dependencias a useMemo) no han cambiado desde el √ļltimo renderizado.

import { useMemo } from 'react';
import { filterTodos } from './utils.js'

export default function TodoList({ todos, theme, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    <div className={theme}>
      <p><b>Nota: ¬°<code>filterTodos</code> se ralentiza artificialmente!</b></p>
      <ul>
        {visibleTodos.map(todo => (
          <li key={todo.id}>
            {todo.completed ?
              <s>{todo.text}</s> :
              todo.text
            }
          </li>
        ))}
      </ul>
    </div>
  );
}


Omitir el rerenderizado de componentes

En algunos casos, useMemo también puede ayudar a optimizar el rendimiento del rerenderizado de componentes hijos. Para ilustrar esto, digamos que este componente TodoList pasa visibleTodos como prop al componente hijo List:

export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}

Te habrás dado cuenta de que alternar la prop theme congela la aplicación por un momento, pero si eliminas <List /> de tu JSX, se siente rápido. Esto te dice que vale la pena intentar optimizar el componente List.

De forma predeterminada, cuando un componente se vuelve a renderizar, React vuelve a renderizar a todos sus hijos de forma recursiva. Por eso, cuando TodoList se vuelve a renderizar con un theme diferente, el componente List tambi√©n se vuelve a renderizar. Esto est√° bien para componentes que no requieren mucho c√°lculo para volverse a renderizar. Pero si has verificado que un nuevo renderizado es lento, puedes decirle a List que omita el nuevo renderizado cuando sus props sean las mismos que en el √ļltimo renderizado envolvi√©ndola en memo:

import { memo } from 'react';

const List = memo(function List({ items }) {
// ...
});

Con este cambio, List omitir√° volver a renderizar si todas sus props son las mismas que en el √ļltimo renderizado. ¬°Aqu√≠ es donde guardar en cach√© el c√°lculo se vuelve importante! Imagina que calculaste visibleTodos sin useMemo:

export default function TodoList({ todos, tab, theme }) {
// Cada vez que cambie el tema, este ser√° una *array* diferente...
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ... por lo que las props de List nunca ser√°n las mismas, y se volver√°n a renderizar cada vez */}
<List items={visibleTodos} />
</div>
);
}

En el ejemplo anterior, la funci√≥n filterTodos siempre crea una array diferente, similar a c√≥mo el objeto literal {} siempre crea un nuevo objeto. Normalmente, esto no ser√≠a un problema, pero significa que las props de List nunca ser√°n las mismas, y tu optimizaci√≥n con memo no funcionar√°. Aqu√≠ es donde useMemo es √ļtil:

export default function TodoList({ todos, tab, theme }) {
// Le dice a React que guarde en caché tu cálculo entre renderizados...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...así que mientras estas dependencias no cambien...
);
return (
<div className={theme}>
{/* ...La lista recibir√° las mismos props y puedes omitir el rerenderizado */}
<List items={visibleTodos} />
</div>
);
}

Al envolver el cálculo de visibleTodos en useMemo, te aseguras de que tenga el mismo valor entre los renderizados (hasta que cambien las dependencias). No tienes que envolver un cálculo en useMemo a menos que lo hagas por alguna razón específica. En este ejemplo, la razón es que lo pasas a un componente envuelto en memo, y esto te permite omitir el rerenderizado. Hay algunas otras razones para agregar useMemo que se describen más adelante en esta página.

Deep Dive

Memoizar nodos individuales de JSX

En lugar de envolver List en memo, podrías envolver el nodo de JSX <List /> en useMemo:

export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]);
return (
<div className={theme}>
{children}
</div>
);
}

El comportamiento sería el mismo. Si visibleTodos no ha cambiado, List no se rerenderizará.

Un nodo de JSX como <List items={visibleTodos} /> es un objeto como { type: List, props: { items: visibleTodos } }. Crear este objeto es muy barato, pero React no sabe si su contenido es el mismo que la √ļltima vez o no. Esta es la raz√≥n por la que, de forma predeterminada, React volver√° a renderizar el componente List.

Sin embargo, si React ve exactamente el mismo JSX que durante el renderizado anterior, no intentará volver a renderizar tu componente. Esto se debe a que los nodos JSX son inmutables. Un objeto de nodo JSX no podría haber cambiado con el tiempo, por lo que React sabe que es seguro omitir un nuevo renderizado. Sin embargo, para que esto funcione, el nodo tiene que ser realmente el mismo objeto, no simplemente tener el mismo aspecto en el código. Esto es lo que hace useMemo en este ejemplo.

Envolver manualmente los nodos JSX en useMemo no es conveniente. Por ejemplo, no puedes hacerlo condicionalmente. Es por esto que lo com√ļn es envolver los componentes con memo en lugar de envolver los nodos de JSX.

La diferencia entre saltarse los renderizados y rerenderizar siempre

Ejemplo 1 de 2:
Omitir el rerenderizado con useMemo y memo

En este ejemplo, el componente List se ralentiza artificialmente para que puedas ver qu√© sucede cuando un componente React que est√°s renderizando es realmente lento. Intenta cambiar las pesta√Īas y alternar el tema.

Cambiar las pesta√Īas se siente lento porque obliga a que List se rerenderice. Eso es de esperar porque tab ha cambiado, y necesitas reflejar la nueva elecci√≥n del usuario en la pantalla.

A continuaci√≥n, intenta alternar el tema. ¬°Gracias a useMemo junto con memo, es r√°pido a pesar de la ralentizaci√≥n artificial! El componente List omiti√≥ rerenderizar porque el array visibleItems no ha cambiado desde el √ļltimo renderizado. El array visibleItems no ha cambiado porque tanto todos como tab (que pasas como dependencias a useMemo) no han cambiado desde el √ļltimo renderizado.

import { useMemo } from 'react';
import List from './List.js';
import { filterTodos } from './utils.js'

export default function TodoList({ todos, theme, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    <div className={theme}>
      <p><b>Nota: ¬°<code>List</code> se ralentiza artificialmente!</b></p>
      <List items={visibleTodos} />
    </div>
  );
}


Memoizar una dependencia de otro Hook

Supón que tienes un cálculo que depende de un objeto creado directamente en el cuerpo del componente:

function Dropdown({ allItems, text }) {
const searchOptions = { matchMode: 'whole-word', text };

const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ūüö© Precauci√≥n: Dependencia de un objeto creado en el cuerpo del componente
// ...

Depender de un objeto como este anula el sentido de la memoizaci√≥n. Cuando un componente se vuelve a renderizar, todo el c√≥digo directamente dentro del cuerpo del componente se vuelve a ejecutar. Las l√≠neas de c√≥digo que crean el objeto searchOptions tambi√©n se ejecutar√°n en cada renderizado. Dado que searchOptions es una dependencia de tu llamada a useMemo, y es diferente cada vez, React sabr√° que las dependencias son diferentes desde la √ļltima vez, y recalcular√° searchItems cada vez.

Para solucionar esto, puedes memoizar al propio objeto searchOptions antes de pasarlo como una dependencia:

function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ‚úÖ Solo cambia cuando cambia el texto

const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ‚úÖ Solo cambia cuando cambia allItems o searchOptions
// ...

En el ejemplo anterior, si text no cambia, el objeto searchOptions tampoco cambiar√°. Sin embargo, una soluci√≥n a√ļn mejor es mover la declaraci√≥n del objeto searchOptions dentro de la funci√≥n de c√°lculo de useMemo:

function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // ‚úÖ Solo cambia cuando cambia `allItems` o text
// ...

Ahora tu c√°lculo depende directamente de text (que es un string y no puede ser ‚Äúaccidentalmente‚ÄĚ nuevo como un objeto). Puedes usar un enfoque similar para evitar que useEffect vuelva a activarse innecesariamente. Antes de intentar optimizar las dependencias con useMemo, ve si puede hacerlas innecesarias. Lee sobre la eliminaci√≥n de dependencias de Efectos.


Memoizar una función

Supongamos que el componente Form está envuelto en memo. Deseas pasarle una función como prop:

export default function ProductPage({ productId, referrer }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}

return <Form onSubmit={handleSubmit} />;
}

De forma similar a cómo {} siempre crea un objeto diferente, declaraciones de funciones como function() {} y expresiones como () => {} producen una función diferente en cada renderizado. Por sí mismo, crear una nueva función no es un problema. ¡Esto no es algo que haya que evitar! Sin embargo, si el componente Form está memoizado, presumiblemente querrás omitir volver a renderizarlo cuando no haya cambiado ninguna prop. Una prop que es siempre diferente anularía el sentido de la memoización.

Para memoizar una función con useMemo, su función de cálculo tendría que devolver otra función:

export default function Page({ productId, referrer }) {
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + product.id + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);

return <Form onSubmit={handleSubmit} />;
}

¬°Esto parece torpe! Memoizar funciones es lo suficientemente com√ļn como para que React tenga un Hook incorporado espec√≠ficamente para eso. Envuelve tus funciones en useCallback en lugar de useMemo para evitar tener que escribir una funci√≥n anidada adicional:

export default function Page({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + product.id + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);

return <Form onSubmit={handleSubmit} />;
}

Los dos ejemplos anteriores son completamente equivalentes. El √ļnico beneficio de useCallback es que te permite evitar escribir una funci√≥n anidada adicional dentro. No hace nada m√°s. Lee m√°s sobre useCallback.


Solución de problemas

Mi c√°lculo se ejecuta dos veces en cada renderizado

En Modo estricto, React llamar√° a algunas de tus funciones dos veces en lugar de una:

function TodoList({ todos, tab }) {
// Esta función de componente se ejecutará dos veces por cada renderizado.

const visibleTodos = useMemo(() => {
// Este c√°lculo se ejecutar√° dos veces si alguna de las dependencias cambia.
return filterTodos(todos, tab);
}, [todos, tab]);

// ...

Esto se espera y no debería romper tu código.

Este comportamiento aplica solo en desarrollo y te ayuda a mantener los componentes puros. React usa el resultado de una de las llamadas e ignora el resultado de la otra llamada. Siempre que tus funciones de componente y cálculo sean puras, no debería afectar tu lógica. Sin embargo, si accidentalmente son impuras, esto te ayuda a detectar los errores y corregirlos.

Por ejemplo, esta función de cálculo impuro muta un array que recibió como prop:

const visibleTodos = useMemo(() => {
// ūüö© Error: mutar la prop
todos.push({ id: 'last', text: 'Go for a walk!' });
const filtered = filterTodos(todos, tab);
return filtered;
}, [todos, tab]);

Debido a que React llama a tu cálculo dos veces, verás que la tarea pendiente se agregó dos veces, por lo que sabrás que hay un error. Tu cálculo no puede cambiar los objetos que recibió, pero puede cambiar cualquier objeto nuevo que haya creado durante el cálculo. Por ejemplo, si filterTodos siempre devuelve un array diferente, puedes mutar ese array:

const visibleTodos = useMemo(() => {
const filtered = filterTodos(todos, tab);
// ‚úÖ Correcto: mutar un objeto que creaste durante el c√°lculo
filtered.push({ id: 'last', text: 'Go for a walk!' });
return filtered;
}, [todos, tab]);

Lee mantener los componentes puros para obtener más información sobre la pureza.

Además, consulte las guías sobre actualización de objetos y actualización de arrays sin mutación.


Se supone que mi llamada a useMemo devuelve un objeto, pero devuelve undefined

Este código no funciona:

// ūüĒī No puedes devolver un objeto desde una funci√≥n flecha con () => {
const searchOptions = useMemo(() => {
matchMode: 'whole-word',
text: text
}, [text]);

En JavaScript, () => { inicia el cuerpo de la función flecha, por lo que la llave { no es parte de tu objeto. Es por eso que no devuelve un objeto y conduce a errores confusos. Podrías arreglarlo agregando paréntesis como ({ y }):

// Esto funciona, pero es f√°cil que alguien lo rompa de nuevo.
const searchOptions = useMemo(() => ({
matchMode: 'whole-word',
text: text
}), [text]);

Sin embargo, esto sigue siendo confuso y demasiado fácil de romper eliminando los paréntesis.

Para evitar este error, escriba una declaración return explícitamente:

// ✅ Esto funciona y es explícito.
const searchOptions = useMemo(() => {
return {
matchMode: 'whole-word',
text: text
};
}, [text]);

Cada vez que mi componente se renderiza, el c√°lculo en useMemo vuelve a ejecutarse

¬°Aseg√ļrate de haber especificado el array de dependencias como segundo argumento!

Si olvidas el array de dependencia, useMemo volver√° a ejecutar el c√°lculo cada vez:

function TodoList({ todos, tab }) {
// ūüĒī Recalcula cada vez: sin *array* de dependencias
const visibleTodos = useMemo(() => filterTodos(todos, tab));
// ...

Esta es la versión corregida que pasa el array de dependencia como segundo argumento:

function TodoList({ todos, tab }) {
// ‚úÖ No recalcula innecesariamente
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...

Si esto no ayuda, entonces el problema es que al menos una de tus dependencias es diferente del renderizado anterior. Puedes depurar este problema registrando manualmente tus dependencias en la consola:

const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]);

Luego, puedes hacer clic derecho en los arrays de diferentes renderizados en la consola y seleccionar ‚ÄúAlmacenar como una variable global‚ÄĚ para ambas. Suponiendo que el primero se guard√≥ como temp1 y el segundo se guard√≥ como temp2, puedes usar la consola del navegador para verificar si cada dependencia en ambos arrays es la misma:

Object.is(temp1[0], temp2[0]); // ¬ŅLa primera dependencia es la misma entre los *arrays*?
Object.is(temp1[1], temp2[1]); // ¬ŅLa segunda dependencia es la misma entre los *arrays*?
Object.is(temp1[2], temp2[2]); // ... y así sucesivamente para cada dependencia ...

Cuando encuentres qué dependencia está interrumpiendo la memoización, busca una manera de eliminarla o memoízala también.


Necesito llamar a useMemo para cada elemento de la lista en un bucle, pero no est√° permitido

Supongamos que el componente Chart est√° envuelto en memo. Quieres omitir volver a renderizar cada Chart en la lista cuando el componente ReportList se vuelve a renderizar. Sin embargo, no puedes llamar a useMemo en un bucle:

function ReportList({ items }) {
return (
<article>
{items.map(item => {
// ūüĒī No puedes llamar a useMemo en un bucle como este:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure key={item.id}>
<Chart data={data} />
</figure>
);
})}
</article>
);
}

En su lugar, extrae un componente para cada elemento y memoiza los datos de elementos individuales:

function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}

function Report({ item }) {
// ‚úÖ Llama a useMemo en el nivel superior:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure>
<Chart data={data} />
</figure>
);
}

Alternativamente, puedes eliminar useMemo y en su lugar envolver Report en memo. Si la prop item no cambia, Report omitirá el rerenderizado, y por tanto Chart también lo hará:

function ReportList({ items }) {
// ...
}

const Report = memo(function Report({ item }) {
const data = calculateReport(item);
return (
<figure>
<Chart data={data} />
</figure>
);
});