# 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 (
{sortedProducts.map(p => (
))}
);
}
```
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 (
);
}
```
## 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 (
onSelect(product.id)}>
{product.name}
);
});
// 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 (
{products.map(p => (
))}
);
}
```
### 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 (
);
}
```
## 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 ;
});
```
**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 (