---
title: "Chapitre 1 : useCallback et useMemo"
weight: 450
---

# Chapitre 1 : useCallback et useMemo

## Le problème : re-rendus inutiles

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

```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

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

### Exemple : calcul coûteux

```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

```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

```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 :

```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

```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

```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é.

```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

```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 :**

```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 :**

```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](/training/module4-react-avance) | [Chapitre suivant : Custom Hooks ->](/training/module4-react-avance/custom-hooks)
