SmarMaker - Documentation
Docs» 15_training:module4-react-avance:usecallback-usememo

Chapitre 1 : useCallback et useMemo

Le problème : re-rendus inutiles

À chaque rendu d'un composant, tout son code est ré-exécuté :

snippet.javascript
function ProductList({ products }) {
    // Recalculé à CHAQUE rendu, même si products n'a pas changé
    const sortedProducts = products.sort((a, b) => a.price - b.price);
 
    // Nouvelle fonction créée à CHAQUE rendu
    const handleClick = (id) => {
        console.log('Clicked:', id);
    };
 
    return (
        <ul>
            {sortedProducts.map(p => (
                <ProductItem key={p.id} product={p} onClick={handleClick} />
            ))}
        </ul>
    );
}

Si products contient 1000 éléments, le tri est fait à chaque rendu, même si rien n'a changé.

useMemo : mémoriser une valeur

useMemo mémorise le résultat d'un calcul et ne le recalcule que si les dépendances changent.

Syntaxe

snippet.javascript
const memoizedValue = useMemo(() => computeValue(a, b), [a, b]);

Exemple : calcul coûteux

snippet.javascript
import { useMemo } from 'react';
 
function ProductList({ products, sortBy }) {
    // Recalculé SEULEMENT si products ou sortBy change
    const sortedProducts = useMemo(() => {
        console.log('Tri en cours...');
        return [...products].sort((a, b) => {
            if (sortBy === 'price') return a.price - b.price;
            if (sortBy === 'name') return a.name.localeCompare(b.name);
            return 0;
        });
    }, [products, sortBy]);
 
    return (
        <ul>
            {sortedProducts.map(p => (
                <li key={p.id}>{p.name} - {p.price}€</li>
            ))}
        </ul>
    );
}

Exemple : filtrage

snippet.javascript
function UserList({ users, searchTerm }) {
    const filteredUsers = useMemo(() => {
        return users.filter(user =>
            user.name.toLowerCase().includes(searchTerm.toLowerCase())
        );
    }, [users, searchTerm]);
 
    return (
        <ul>
            {filteredUsers.map(user => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
}

useCallback : mémoriser une fonction

useCallback mémorise une fonction et ne la recrée que si les dépendances changent.

Syntaxe

snippet.javascript
const memoizedFn = useCallback(() => {
    doSomething(a, b);
}, [a, b]);

Pourquoi c'est utile ?

En JavaScript, chaque fois qu'une fonction est définie, c'est une nouvelle référence :

snippet.javascript
const fn1 = () => console.log('hello');
const fn2 = () => console.log('hello');
 
fn1 === fn2;  // false ! Ce sont deux fonctions différentes

Cela pose problème avec React.memo et les dépendances de useEffect.

Exemple avec React.memo

snippet.javascript
// Composant enfant mémoïsé
const ProductItem = React.memo(function ProductItem({ product, onSelect }) {
    console.log('ProductItem rendu:', product.name);
    return (
        <li onClick={() => onSelect(product.id)}>
            {product.name}
        </li>
    );
});
 
// Composant parent
function ProductList({ products }) {
    const [selected, setSelected] = useState(null);
 
    // SANS useCallback : nouvelle fonction à chaque rendu
    // → ProductItem est re-rendu même si product n'a pas changé
    const handleSelect = (id) => {
        setSelected(id);
    };
 
    // AVEC useCallback : même fonction tant que les dépendances ne changent pas
    const handleSelectMemo = useCallback((id) => {
        setSelected(id);
    }, []);  // [] car setSelected est stable
 
    return (
        <ul>
            {products.map(p => (
                <ProductItem
                    key={p.id}
                    product={p}
                    onSelect={handleSelectMemo}
                />
            ))}
        </ul>
    );
}

Exemple avec dépendances

snippet.javascript
function SearchForm({ onSearch, category }) {
    const [query, setQuery] = useState('');
 
    // La fonction dépend de category
    const handleSubmit = useCallback(() => {
        onSearch(query, category);
    }, [onSearch, query, category]);
 
    return (
        <form onSubmit={handleSubmit}>
            <input
                value={query}
                onChange={(e) => setQuery(e.target.value)}
            />
            <button type="submit">Rechercher</button>
        </form>
    );
}

React.memo : mémoriser un composant

React.memo empêche un composant de se re-rendre si ses props n'ont pas changé.

snippet.javascript
const MyComponent = React.memo(function MyComponent({ name, onClick }) {
    console.log('Rendu de MyComponent');
    return <button onClick={onClick}>{name}</button>;
});

Important : React.memo compare les props par référence. Si une fonction est recréée à chaque rendu, le composant sera quand même re-rendu.

Quand utiliser useMemo ?

OUI :

  • Calculs coûteux (tri, filtrage de grandes listes)
  • Création d'objets passés en props à des composants mémoïsés
  • Calculs dont le résultat est utilisé dans plusieurs endroits

NON :

  • Calculs simples (addition, concaténation)
  • Composants qui se re-rendent de toute façon
  • Par défaut “au cas où”

Quand utiliser useCallback ?

OUI :

  • Fonctions passées à des composants enfants mémoïsés (React.memo)
  • Fonctions dans les dépendances de useEffect
  • Gestionnaires d'événements coûteux

NON :

  • Fonctions utilisées uniquement localement
  • Composants enfants non mémoïsés
  • Par défaut “pour optimiser”

Comparaison

Hook Mémorise Recalcule si
useMemo Une valeur Les dépendances changent
useCallback Une fonction Les dépendances changent
React.memo Un composant Les props changent

Piège : dépendances incorrectes

snippet.javascript
// PROBLÈME : items manque dans les dépendances
const getTotal = useCallback(() => {
    return items.reduce((sum, item) => sum + item.price, 0);
}, []);  // items devrait être dans le tableau !
 
// CORRECT
const getTotal = useCallback(() => {
    return items.reduce((sum, item) => sum + item.price, 0);
}, [items]);

ESLint avec le plugin eslint-plugin-react-hooks vous avertira des dépendances manquantes.

Exercices

Exercice 1 : Liste filtrée optimisée

Créer une liste de produits avec un champ de recherche. Optimiser pour ne pas refiltrer si la recherche n'a pas changé.

Solution :

snippet.javascript
function OptimizedProductList({ products }) {
    const [search, setSearch] = useState('');
    const [sortBy, setSortBy] = useState('name');
 
    const filteredAndSorted = useMemo(() => {
        console.log('Filtrage et tri...');
        return products
            .filter(p => p.name.toLowerCase().includes(search.toLowerCase()))
            .sort((a, b) => {
                if (sortBy === 'name') return a.name.localeCompare(b.name);
                if (sortBy === 'price') return a.price - b.price;
                return 0;
            });
    }, [products, search, sortBy]);
 
    return (
        <div>
            <input
                value={search}
                onChange={(e) => setSearch(e.target.value)}
                placeholder="Rechercher..."
            />
            <select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
                <option value="name">Par nom</option>
                <option value="price">Par prix</option>
            </select>
            <ul>
                {filteredAndSorted.map(p => (
                    <li key={p.id}>{p.name} - {p.price}€</li>
                ))}
            </ul>
        </div>
    );
}

Exercice 2 : Callback stable

Créer un composant parent avec plusieurs boutons enfants. Optimiser pour que les boutons ne se re-rendent pas inutilement.

Solution :

snippet.javascript
const Button = React.memo(function Button({ id, onClick, children }) {
    console.log('Button rendu:', id);
    return <button onClick={() => onClick(id)}>{children}</button>;
});
 
function ButtonGroup() {
    const [clicked, setClicked] = useState(null);
 
    const handleClick = useCallback((id) => {
        setClicked(id);
        console.log('Bouton cliqué:', id);
    }, []);
 
    return (
        <div>
            <p>Dernier clic : {clicked}</p>
            <Button id="a" onClick={handleClick}>Bouton A</Button>
            <Button id="b" onClick={handleClick}>Bouton B</Button>
            <Button id="c" onClick={handleClick}>Bouton C</Button>
        </div>
    );
}

Points clés à retenir

  1. useMemo mémorise une valeur calculée
  2. useCallback mémorise une fonction
  3. React.memo mémorise un composant
  4. Ne pas optimiser prématurément - mesurer d'abord
  5. Dépendances complètes - toujours lister toutes les valeurs utilisées

← Retour au module | Chapitre suivant : Custom Hooks →

Previous Next

Made with ❤ by CAP-REL · SmartMaker · GNU AGPL v3+
Code source · Faire un don
SmarMaker - Documentation
Traductions de cette page:
  • Français
  • Deutsch
  • English
  • Español
  • Italiano
  • Nederlands

Table of Contents

Table des matières

  • Chapitre 1 : useCallback et useMemo
    • Le problème : re-rendus inutiles
    • useMemo : mémoriser une valeur
      • Syntaxe
      • Exemple : calcul coûteux
      • Exemple : filtrage
    • useCallback : mémoriser une fonction
      • Syntaxe
      • Pourquoi c'est utile ?
      • Exemple avec React.memo
      • Exemple avec dépendances
    • React.memo : mémoriser un composant
    • Quand utiliser useMemo ?
    • Quand utiliser useCallback ?
    • Comparaison
    • Piège : dépendances incorrectes
    • Exercices
      • Exercice 1 : Liste filtrée optimisée
      • Exercice 2 : Callback stable
    • Points clés à retenir
  • SmartAuth
  • SmartMaker - Back (PHP)
    • Mapping Dolibarr - React
  • SmartMaker - Front (React)
    • Animations de pages
    • Architecture
    • Astuces
    • Calendar
    • Composants et pages
    • Configuration du Provider
    • Debug et Logs
    • Hooks SmartCommon
    • PWA (Progressive Web App)
    • Requêtes API
    • Routage
    • SmartCommon
    • Stockage de données
    • Thèmes
    • Traductions
  • HowTo - Pas à pas - Votre première application
    • Développement PHP (back)
    • Développement React (front)
    • Première étape : Module Builder Dolibarr
    • SmartAuth
    • SmartBoot : Un "squelette" quasiment prêt à l'emploi
  • Formation SmartMaker
    • Module 1 : Fondamentaux JavaScript ES6+
      • Chapitre 1 : Variables et Scope
      • Chapitre 2 : Fonctions
      • Chapitre 3 : Programmation Asynchrone
      • Chapitre 4 : Modules ES6
    • Module 2 : Introduction à React
      • Chapitre 1 : Philosophie React
      • Chapitre 2 : JSX
      • Chapitre 3 : Composants
    • Module 3 : Hooks React Fondamentaux
      • Chapitre 1 : useState
      • Chapitre 2 : useEffect
      • Chapitre 3 : useRef
      • Chapitre 4 : useContext
    • Module 4 : React Avancé
      • Chapitre 1 : useCallback et useMemo
      • Chapitre 2 : Custom Hooks
      • Chapitre 3 : Redux et Redux Toolkit
    • Module 5 : Architecture SmartMaker
      • Chapitre 1 : Structure du projet
      • Chapitre 2 : Configuration
      • Chapitre 3 : Flux de données
    • Module 6 : SmartCommon - Composants
      • Chapitre 1 : Mise en page
      • Chapitre 2 : Navigation
      • Chapitre 3 : Formulaires
      • Chapitre 4 : Affichage
    • Module 7 : SmartCommon - Hooks
      • Chapitre 1 : useApi
      • Chapitre 2 : Gestion d'état
      • Chapitre 3 : Hooks utilitaires
    • Module 8 : Backend API (PHP)
      • Chapitre 1 : Routage
      • Chapitre 2 : Controllers
      • Chapitre 3 : Mappers
      • Extrafields et formulaires dynamiques
    • Module 9 : Intégration complète
      • Chapitre 1 : Backend
      • Chapitre 2 : Frontend
      • Chapitre 3 : Déploiement
    • Module 10 : Fonctionnalités avancées
      • Chapitre 1 : Mode offline
      • Chapitre 2 : Internationalisation (i18n)
      • Chapitre 3 : Autres fonctionnalités
    • Module 11 : Bonnes pratiques
  • Démonstration
  • Start
  • Composants et pages
  • Afficher le texte source
  • Anciennes révisions
  • Liens de retour
  • Haut de page