useMemo est un Hook React qui vous permet de mettre en cache le résultat d’un calcul d’un rendu à l’autre.

const cachedValue = useMemo(calculateValue, dependencies)

Référence

useMemo(calculateValue, dependencies)

Appelez useMemo à la racine de votre composant pour mettre en cache un calcul d’un rendu à l’autre :

import { useMemo } from 'react';

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

Voir d’autres exemples ci-dessous.

Paramètres

  • calculateValue : la fonction qui calcule la valeur que vous souhaitez mettre en cache. Elle doit être pure, ne prendre aucun argument, et peut renvoyer n’importe quel type de valeur. React appellera votre fonction lors du rendu initial. Lors des rendus suivants, React vous renverra cette même valeur tant que les dependencies n’auront pas changé depuis le rendu précédent. Dans le cas contraire, il appellera calculateValue, renverra sa valeur, et la stockera pour la réutiliser par la suite.

  • dependencies : la liste des valeurs réactives référencées par le code de calculateValue. Les valeurs réactives comprennent les props, les variables d’état et toutes les variables et fonctions déclarées localement dans le corps de votre composant. Si votre linter est configuré pour React, il vérifiera que chaque valeur réactive concernée est bien spécifiée comme dépendance. La liste des dépendances doit avoir un nombre constant d’éléments et utiliser un littéral défini à la volée, du genre [dep1, dep2, dep3]. React comparera chaque dépendance à sa valeur précédente au moyen de la comparaison Object.is.

Valeur renvoyée

Lors du rendu initial, useMemo renvoie le résultat d’un appel sans arguments à calculateValue.

Lors des rendus ultérieurs, soit il vous renverra la valeur résultat mise en cache jusqu’ici (si les dépendances n’ont pas changé), soit il rappellera calculateValue et vous renverra la valeur ainsi obtenue.

Limitations

  • useMemo est un Hook, vous pouvez donc uniquement l’appeler à la racine de votre composant ou de vos propres Hooks. Vous ne pouvez pas l’appeler à l’intérieur de boucles ou de conditions. Si nécessaire, extrayez un nouveau composant et déplacez l’Effet dans celui-ci.

  • React ne libèrera pas la valeur mise en cache s’il n’a pas une raison bien précise de le faire. Par exemple, en développement, React vide le cache dès que vous modifiez le fichier de votre composant. Et tant en développement qu’en production, React vide le cache si votre composant suspend lors du montage initial. À l’avenir, React est susceptible d’ajouter de nouvelles fonctionnalités qui tireront parti du vidage de cache — si par exemple React prenait un jour nativement en charge la virtualisation des listes, il serait logique qu’il retire du cache les éléments défilant hors de la zone visible de la liste virtualisée. Ça devrait correspondre à vos attentes si vous concevez useMemo comme une optimisation de performance. Dans le cas contraire, vous voudrez sans doute plutôt recourir à une variable d’état ou à une ref.

Remarque

La mise en cache des valeurs renvoyées est également appelée mémoïsation, c’est pourquoi ce Hook s’appelle useMemo.


Utilisation

Sauter des recalculs coûteux

Pour mettre en cache un calcul d’un rendu à l’autre, enrobez son appel avec le Hook useMemo à la racine de votre composant :

import { useMemo } from 'react';

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

Vous devez passer deux arguments à useMemo :

  1. Une fonction de calcul qui ne prend pas d’argument, comme par exemple () =>, et qui renvoie la valeur que vous souhaitez calculer.
  2. Une liste de dépendances comprenant chaque valeur issue de votre composant que cette fonction utilise.

Lors du rendu initial, la valeur renvoyée par useMemo sera le résultat de l’appel au calcul.

Lors des rendus suivants, React comparera les dépendances avec celles passées lors du rendu précédent. Si aucune dépendance n’a changé (sur base d’une comparaison avec l’algorithme Object.is), useMemo continuera à utiliser la même valeur déjà calculée. Dans le cas contraire, React refera le calcul et vous renverra la nouvelle valeur.

En d’autres termes, useMemo met en cache un calcul de valeur d’un rendu à l’autre jusqu’à ce que ses dépendances changent.

Déroulons un exemple afin de comprendre en quoi c’est utile.

Par défaut, React re-exécutera l’intégralite du corps de votre composant à chaque nouveau rendu. Par exemple, si TodoList met à jour son état ou reçoit de nouvelles props de son parent, la fonction filterTodos sera appelée à nouveau :

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

En temps normal, ça n’est pas un souci car la majorité des calculs sont très rapides. Cependant, si vous filtrez ou transformez un énorme tableau, ou procédez à des calculs coûteux, vous pourriez vouloir éviter de les refaire alors que les données n’ont pas changé. Si tant todos que tab sont identiques à leurs valeurs du rendu précédent, enrober le calcul avec useMemo comme vu plus haut vous permet de réutiliser les visibleTodos déjà calculées pour ces données.

Ce type de mise en cache est appelée mémoïsation.

Remarque

Vous ne devriez recourir à useMemo que pour optimiser les performances. Si votre code ne fonctionne pas sans lui, commencez par débusquer la cause racine puis corrigez-la. Alors seulement envisagez de remettre useMemo.

En détail

Comment savoir si un calcul est coûteux ?

En règle générale, à moins que vous ne créiez ou itériez à travers des milliers d’objets, ça n’est probablement pas coûteux. Si vous avez besoin de vous rassurer, vous pouvez ajouter une mesure en console du temps passé dans ce bout de code :

console.time('filtrage tableau');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filtrage tableau');

Réalisez l’interaction à mesurer (par exemple, saisissez quelque chose dans un champ). Vous verrez alors un message en console du genre filtrage tableau: 0.15ms. Si le temps cumulé obtenu devient important (disons 1ms ou plus), il peut être pertinent de mémoïser le calcul. À titre d’expérience, vous pouvez enrober le calcul avec useMemo pour vérifier si le temps total mesuré s’est réduit ou non pour votre interaction :

console.time('filtrage tableau');
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab); // Sauté si todos et tab n’ont pas changé
}, [todos, tab]);
console.timeEnd('filtrage tableau');

useMemo n’accélèrera pas le premier rendu. Il aide seulement à sauter un traitement superflu lors des mises à jour.

Gardez à l’esprit que votre machine est probablement plus rapide que celles de vos utilisateurs, il est donc recommandé de tester la performance au sein d’un ralentissement artificiel. Par exemple, Chrome propose une option de bridage processeur exprès pour ça.

Remarquez aussi que mesurer la performance en développement ne vous donnera pas des résultats très précis. (Par exemple, quand le Mode Strict est actif, chaque composant fait deux rendus au lieu d’un.) Pour améliorer la pertinence de vos mesures, construisez la version de production de votre appli et testez-la sur des appareils similaires à ceux de vos utilisateurs.

En détail

Devriez-vous mettre des useMemo partout ?

Si votre appli est comme ce site, l’essentiel des interactions ont un impact assez large (genre remplacer une page ou une section entière), de sorte que la mémoïsation est rarement nécessaire. En revanche, si votre appli est plus comme un éditeur de dessin, et que la plupart des interactions sont granulaires (comme déplacer des formes), alors la mémoïsation est susceptible de beaucoup vous aider.

Optimiser avec useMemo n’est utile que dans trois grands cas de figure :

  • Le calcul que vous enrobez avec useMemo est d’une lenteur perceptible, alors même que ses dépendances changent peu.
  • Vous passez la valeur à un composant enrobé avec memo. Vous voulez qu’il puisse éviter de refaire son rendu si la valeur n’a pas changé. En mémoïsant le calcul, vous limitez ses nouveaux rendus aux cas où les dépendances de celui-ci ont en effet changé.
  • La fonction que vous passez est utilisée plus loin comme dépendance par un Hook. Par exemple, un autre calcul enrobé par useMemo en dépend, ou vous en dépendez pour un useEffect.

Le reste du temps, enrober un calcul avec useMemo n’a pas d’intérêt. Ça ne va pas gêner non plus, aussi certaines équipes décident de ne pas réfléchir au cas par cas, et mémoïsent autant que possible. L’inconvénient, c’est que ça nuit à la lisibilité du code. Par ailleurs, toutes les mémoïsations ne sont pas efficaces. Il suffit qu’une seule valeur utilisée soit « toujours différente » pour casser la mémoïsation de tout un composant.

Remarquez que useMemo n’empêche pas la création de la fonction. Vous créez la fonction à chaque rendu (et tout va bien !) mais React l’ignorera et vous renverra la fonction mise en cache si aucune dépendance n’a changé.

En pratique, vous pouvez rendre beaucoup de mémoïsations superflues rien qu’en respectant les principes suivants :

  1. Lorsqu’un composant en enrobe d’autres visuellement, permettez-lui d’accepter du JSX comme enfant. Ainsi, si le composant d’enrobage met à jour son propre état, React saura que ses enfants n’ont pas besoin de refaire leur rendu.
  2. Préférez l’état local et ne faites pas remonter l’état plus haut que nécessaire. Ne conservez pas les éléments d’état transients (tels que les champs de formulaire ou l’état de survol d’un élément) à la racine de votre arbre ou dans une bibliothèque de gestion d’état global.
  3. Assurez-vous d’avoir une logique de rendu pure. Si refaire le rendu d’un composant entraîne des problèmes ou produit un artefact visuel perceptible, c’est un bug dans votre composant ! Corrigez le bug plutôt que de tenter de le cacher avec une mémoïsation.
  4. Évitez les Effets superflus qui mettent à jour l’état. La plupart des problèmes de performance des applis React viennent de chaînes de mise à jour issues d’Effets, qui entraînent de multiples rendus consécutifs de vos composants.
  5. Essayez d’alléger les dépendances de vos Effets. Par exemple, plutôt que de mémoïser, il est souvent plus simple de déplacer un objet ou une fonction à l’intérieur de l’Effet voire hors de votre composant.

Si une interaction spécifique continue à traîner la patte, utilisez le Profiler des outils de développement React pour découvrir quels composants bénéficieraient le plus d’une mémoïsation, et ajoutez-en au cas par cas. Ces principes facilitent le débogage et la maintenabilité de vos composants, ils sont donc utiles à suivre dans tous les cas. À plus long terme, nous faisons de la recherche sur les moyens de mémoïser automatiquement pour résoudre ces questions une bonne fois pour toutes.

La différence entre useMemo et calculer la valeur directement

Exemple 1 sur 2 ·
Éviter de recalculer avec useMemo

Dans cet exemple, la fonction filterTodos est artificiellement ralentie pour que vous puissiez bien voir ce qui se passe lorsqu’une fonction JavaScript que vous appelez est véritablement lente. Essayez donc de changer d’onglet ou de basculer le thème actif.

Le changement d’onglet semble lent parce qu’il force l’exécution de la fonction filterTodos ralentie. On pouvait s’y attendre, puisque l’onglet a changé, le calcul a donc besoin d’être refait. (Si vous vous demandez pourquoi il est exécuté deux fois, on vous l’explique ici.)

Essayez maintenant de basculer le thème. Grâce à useMemo, c’est rapide en dépit du ralenti artificiel ! L’appel lent à filterTodos est évité parce que ni todos ni tab (les dépendances déclarées pour le useMemo) n’ont changé depuis le dernier rendu.

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>Remarque : <code>filterTodos</code> est artificiellement ralentie !</b></p>
      <ul>
        {visibleTodos.map(todo => (
          <li key={todo.id}>
            {todo.completed ?
              <s>{todo.text}</s> :
              todo.text
            }
          </li>
        ))}
      </ul>
    </div>
  );
}


Éviter les rendus superflus de composants

Dans certains cas, useMemo peut aussi vous aider à optimiser la performance liée aux nouveaux rendus de composants enfants. Pour illustrer ceci, supposons que vous ayez un composant TodoList qui passe une prop visibleTodos à son composant enfant List :

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

En utilisant l’interface, vous avez remarqué que basculer la prop theme gèle l’appli pendant un moment, mais si vous retirez <List /> de votre JSX, il redevient performant. Ça vous indique qu’il serait bon de tenter d’optimiser le composant List.

Par défaut, lorsqu’un composant refait son rendu, React refait le rendu de tous ses composants enfants, récursivement. C’est pourquoi lorsque TodoList refait son rendu avec un theme différent, le composant List refait aussi son rendu. Ça ne pose aucun problème pour les composants dont le rendu n’est pas trop coûteux. Mais si vous avez confirmé que son rendu est lent, vous pouvez dire à List d’éviter de nouveaux rendus lorsque ses props ne changent pas en l’enrobant avec memo :

import { memo } from 'react';

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

Avec cet ajustement, List évitera de refaire son rendu si toutes ses propriétés sont identiques depuis le dernier rendu. Et c’est là que la mise en cache du calcul devient importante ! Imaginons que vous définissiez visibleTodos sans useMemo :

export default function TodoList({ todos, tab, theme }) {
// Chaque fois que le `theme` change, ça donnera un nouveau tableau...
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ... du coup les props de List seront toujours différentes,
et il refera son rendu à chaque fois. */}
<List items={visibleTodos} />
</div>
);
}

Dans l’exemple ci-dessus, la fonction filterTodos crée toujours un tableau différent, de la même façon qu’un littéral objet {} crée toujours un nouvel objet. En temps normal ça ne poserait pas problème, mais ici ça signifie que les props de List ne seront jamais identiques, de sorte que votre optimisation avec memo ne servira à rien. C’est là que useMemo entre en scène :

export default function TodoList({ todos, tab, theme }) {
// Dit à React de mettre en cache votre calcul d’un rendu à l’autre...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...du coup tant que ces dépendances ne changent pas...
);
return (
<div className={theme}>
{/* ...List recevra les mêmes props et ne refera pas son rendu. */}
<List items={visibleTodos} />
</div>
);
}

En enrobant le calcul de visibleTodos dans un useMemo, vous garantissez qu’il s’agira de la même valeur d’un rendu à l’autre (tant que les dépendances ne changent pas). Vous n’avez pas besoin d’enrober un calcul dans useMemo par défaut, sans raison précise. Dans cet exemple, la raison tient à ce que vous la passez à un composant enrobé par memo, ça lui permet donc d’effectivement éviter des rendus superflus. Il existe d’autres raisons de recourir à useMemo, qui sont détaillées dans la suite de cette page.

En détail

Mémoïser des nœuds JSX spécifiques

Plutôt que d’enrober List dans memo, vous pourriez vouloir enrober le nœud JSX <List /> lui-même dans un 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>
);
}

Le comportement serait identique. Si visibleTodos n’a pas changé, List ne refera pas son rendu.

Un nœud JSX comme <List items={visibleTodos} /> est un objet du genre { type: List, props: { items: visibleTodos } }. Créer cet objet a un coût quasiment nul, mais React ne sait pas si son contenu est identique ou non à celui de la dernière fois. C’est pourquoi, par défaut, React refera le rendu du composant List.

En revanche, si React voit exactement le même JSX que lors du précédent rendu, il ne tentera pas de refaire le rendu de votre composant. C’est parce que les nœuds JSX sont immuables. Un objet de nœud JSX n’aurait pas pu changer avec le temps, React peut donc supposer sereinement qu’il peut sauter le rendu. Ceci dit, pour que ça fonctionne, il doit s’agir exactement du même objet nœud en mémoire, pas simplement d’un objet de structure identique. C’est à ça que sert useMemo dans cet exemple.

Enrober manuellement des nœuds JSX avec useMemo n’est pas très pratique. Par exemple, on ne peut pas le faire conditionnellement. C’est pourquoi vous enroberez généralement la définition du composant avec memo plutôt que d’enrober des nœuds JSX individuels.

La différence entre sauter des rendus superflus et toujours refaire le rendu

Exemple 1 sur 2 ·
Éviter les rendus superflus avec useMemo et memo

Dans cet exemple, le composant List est artificiellement ralenti pour que vous puissiez bien voir ce qui se passe lorsque le rendu d’un composant React est véritablement lent. Essayez de changer d’onglet ou de basculer le thème.

Le changement d’onglet semble lent parce qu’elle force le List ralenti à refaire son rendu. On pouvait s’y attendre, puisque l’onglet a changé, vous devez donc refléter le nouveau choix de l’utilisateur à l’écran.

Essayez maintenant de basculer le thème. Grâce à la combinaison de useMemo et memo, c’est rapide en dépit du ralenti artificiel ! List a évité un nouveau rendu parce que le tableau visibleTodos n’a pas changé, dans la mesure où ni todos ni tab (les dépendances déclarées pour le useMemo) n’ont changé depuis le dernier rendu.

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>Remarque : <code>List</code> est artificiellement ralenti !</b></p>
      <List items={visibleTodos} />
    </div>
  );
}


Mémoïser une dépendance d’un autre Hook

Supposons que vous ayez un calcul qui dépende d’un objet créé directement au sein du composant :

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

const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 Attention : dépend d’un objet créé dans le corps du composant
// ...

Dépendre ainsi d’un objet coupe l’herbe sous le pied à la mémoïsation. Lorsqu’un composant refait son rendu, tout le code directement au sein du corps du composant est exécuté à nouveau. Les lignes de code qui créent l’objet searchOptions sont aussi exécutées pour chaque nouveau rendu. Dans la mesure où searchOptions est une dépendance dans l’appel à useMemo, vu qu’il est différent à chaque fois, les dépendances connues de React seront différentes à chaque fois, et on recalculera searchItems à chaque fois.

Pour corriger ça, vous pourriez choisir de mémoïser l’objet searchOptions lui-même avant de le passer comme dépendance à l’autre Hook :

function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ Ne change que si le texte change

const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ Ne change que si `allItems` ou `searchOptions` changent
// ...

Dans l’exemple ci-dessus, si le text ne change pas, l’objet searchOptions ne changera pas non plus. Ceci dit, il serait encore préférable de déplacer la déclaration de l’objet searchOptions au sein de la fonction de calcul enrobée par useMemo :

function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // ✅ Ne change que si `allItems` ou `text` changent
// ...

À présent votre calcul dépend directement de text (qui est une chaîne de caractères et ne peut pas être un objet différent « par inadvertance »).


Mémoïser une fonction

Supposons qu’un composant Form soit enrobé par memo. Vous souhaitez lui passer une fonction comme prop :

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

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

Tout comme {} crée un nouvel objet à chaque fois, les déclarations et expressions de fonctions comme function() {} et () => {} produisent une fonction différente à chaque rendu. En soi, créer ces fonctions n’est pas un problème. Vous ne devriez pas chercher à l’éviter à tout prix ! En revanche, si le composant Form est mémoïsé, vous souhaitez sans doute éviter ses rendus superflus lorsque les props n’ont pas changé. Une prop qui est toujours différente empêcherait forcément sa mémoïsation.

Pour mémoïser une fonction avec useMemo, la fonction de calcul que vous passez ay Hook aurait besoin de renvoyer la fonction à mémoïser :

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

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

Ce code est pataud ! Mémoïser des fonctions est un besoin suffisamment courant pour que React propose un Hook dédié pour ça. Enrobez vos fonctions avecuseCallback plutôt que useMemo pour éviter d’avoir à écrire un niveau supplémentaire d’imbrication de fonction :

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

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

Ces deux exemples sont parfaitement équivalents. Le seul avantage de useCallback, c’est qu’il vous évite de devoir enrober votre fonction dans une autre. Il ne fait rien de plus. Apprenez-en davantage sur useCallback.


Dépannage

Mon calcul est exécuté deux fois par rendu

En Mode Strict, React appellera certaines de vos fonctions deux fois plutôt qu’une :

function TodoList({ todos, tab }) {
// Cette fonction composant sera appelée deux fois par rendu.

const visibleTodos = useMemo(() => {
// Ce calcul sera exécuté deux fois pour tout changement de dépendances.
return filterTodos(todos, tab);
}, [todos, tab]);

// ...

C’est intentionnel, et ça ne devrait pas casser votre code.

Ce comportement spécifique au développement vous aide à garder les composants purs. React utilise le résultat de l’un des appels et ignore l’autre. Tant que votre composant et votre fonction de calcul sont purs, ça ne devrait pas affecter votre logique. Si toutefois ils sont malencontreusement impurs, ça vous permettra de détecter les erreurs.

Par exemple, cette fonction calcul impure modifie directement un tableau reçu comme prop :

const visibleTodos = useMemo(() => {
// 🚩 Erreur : modification directe (en place) d'une prop
todos.push({ id: 'last', text: 'Aller faire un tour !' });
const filtered = filterTodos(todos, tab);
return filtered;
}, [todos, tab]);

React appelle votre fonction deux fois, afin que vous remarquiez que la tâche est ajoutée deux fois. Votre calcul ne devrait pas modifier des objets existants, mais vous pouvez très bien modifier des nouveaux objets créés lors du calcul. Par exemple, si la fonction filterTodos renvoie toujours un tableau différent, vous pouvez modifier plutôt ce tableau-là :

const visibleTodos = useMemo(() => {
const filtered = filterTodos(todos, tab);
// ✅ Correct : modification d'un objet créé lors du calcul
filtered.push({ id: 'last', text: 'Aller faire un tour !' });
return filtered;
}, [todos, tab]);

Lisez Garder les composants purs pour en apprendre davantage.

Jetez donc aussi un coup d’œil aux guides pour mettre à jour les objets et mettre à jour les tableaux sans les modifier directement.


Mon appel à useMemo call est censé renvoyer un objet, mais il renvoie undefined

Ce code ne fonctionne pas :

// 🔴 La syntaxe `() => {` ne renvoie pas un objet
const searchOptions = useMemo(() => {
matchMode: 'whole-word',
text: text
}, [text]);

En JavaScript, () => { démarre le corps de la fonction fléchée, de sorte que l’accolade ouvrante { ne marque pas le début d’un objet. C’est pourquoi ça ne renvoie pas un objet, entraînant des erreurs. Vous pourriez corriger ça en enrobant les accolades par des parenthèses, avec ({ et }) :

// Fonctionne, mais reste facile à casser par un tiers
const searchOptions = useMemo(() => ({
matchMode: 'whole-word',
text: text
}), [text]);

Ceci dit, ça reste suffisamment déroutant pour que quelqu’un d’autre le casse à nouveau plus tard, en retirant les parenthèses. Pour éviter ça, écrivez une instruction return explicite :

// ✅ Fonctionne et rend l’intention explicite
const searchOptions = useMemo(() => {
return {
matchMode: 'whole-word',
text: text
};
}, [text]);

À chaque rendu de mon composant, le calcul dans useMemo est exécuté

Assurez-vous d’avoir spécifié le tableau de dépendances comme second argument !

Si vous oubliez le tableau de dépendances, useMemo exécutera le calcul à chaque fois :

function TodoList({ todos, tab }) {
// 🔴 Recalcule à chaque fois, faute de tableau de dépendances
const visibleTodos = useMemo(() => filterTodos(todos, tab));
// ...

Voici la version corrigée, qui passe bien le tableau de dépendances comme second argument :

function TodoList({ todos, tab }) {
// ✅ Ne fait pas de recalcul superflu
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...

Si ça n’aide pas, alors le problème vient de ce qu’au moins une de vos dépendances diffère depuis le rendu précédent. Vous pouvez déboguer ce problème en affichant manuellement vos dépendances dans la console :

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

Vous pouvez alors cliquer bouton droit, dans la console, sur les tableaux issus de différents rendus et sélectionner « Stocker objet en tant que variable globale » pour chacun d’entre eux. En supposant que vous avez stocké le premier en tant que temp1 et le second en tant que temp2, vous pouvez alors utiliser la console du navigateur pour vérifier si chaque dépendance des tableaux est identique :

Object.is(temp1[0], temp2[0]); // La première dépendance est-elle inchangée ?
Object.is(temp1[1], temp2[1]); // La deuxième dépendance est-elle inchangée ?
Object.is(temp1[2], temp2[2]); // ... et ainsi de suite pour chaque dépendance ...

Lorsque vous aurez repéré la dépendance qui casse la mémoïsation, vous pouvez soit tenter de la retirer, soit la mémoïser aussi.


Je souhaite appeler useMemo pour chaque élément d’une liste dans une boucle, mais c’est interdit

Imaginez que le composant Chart utilisé ci-dessous soit enrobé par memo. Vous souhaitez éviter des rendus superflus de chaque Chart dans la liste lorsque le composant ReportList refait son rendu. Cependant, vous ne pouvez pas appeler useMemo au sein de la boucle :

function ReportList({ items }) {
return (
<article>
{items.map(item => {
// 🔴 Vous n’avez pas le droit d’utiliser `useMemo` dans une boucle comme ceci :
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure key={item.id}>
<Chart data={data} />
</figure>
);
})}
</article>
);
}

Au lieu de ça, extrayez un composant pour chaque élément, et mémoïsez les données individuellement pour chaque élément :

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

function Report({ item }) {
// ✅ Appelez `useMemo` au niveau racine :
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure>
<Chart data={data} />
</figure>
);
}

Une autre solution consisterait à retirer useMemo de l’exemple précédent, pour plutôt enrober Report lui-même avec un memo. Ainsi, si la prop item ne change pas, Report évitera de refaire son rendu, de sorte que Chart sera épargné lui aussi :

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

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