React - useCallback : Mémorisation de Fonctions
Quand tu passes des fonctions en props à des composants memo(), useCallback va mémoriser la fonction et éviter les re-renders inutiles.
Le Problème des Fonctions Recréées
Nouvelles Fonctions à Chaque Rendu
function ProblematicParent() {
const [count, setCount] = useState(0)
const [items, setItems] = useState([])
const [name, setName] = useState('')
// ❌ PROBLÈME - Nouvelle fonction à chaque rendu !
const addItem = (text) => {
setItems(prev => [...prev, { id: Date.now(), text }])
}
// ❌ PROBLÈME - Nouvelle fonction à chaque rendu !
const handleClick = (id) => {
console.log('Item clicked:', id)
}
// ❌ PROBLÈME - Nouvelle fonction à chaque rendu !
const handleSubmit = (e) => {
e.preventDefault()
console.log('Form submitted')
}
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
{/* Ces composants re-render même si leurs props logiques n'ont pas changé ! */}
<ExpensiveForm onSubmit={handleSubmit} />
<ExpensiveList items={items} onItemClick={handleClick} />
<ExpensiveAddButton onAdd={addItem} />
</div>
)
}
// Ces composants optimisés ne servent à rien car les fonctions changent toujours !
const ExpensiveForm = memo(function ExpensiveForm({ onSubmit }) {
console.log('📝 ExpensiveForm re-rendu') // ← Toujours appelé !
return <form onSubmit={onSubmit}><button>Submit</button></form>
})
const ExpensiveList = memo(function ExpensiveList({ items, onItemClick }) {
console.log('📝 ExpensiveList re-rendu') // ← Toujours appelé !
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => onItemClick(item.id)}>
{item.text}
</li>
))}
</ul>
)
})
const ExpensiveAddButton = memo(function ExpensiveAddButton({ onAdd }) {
console.log('🔘 ExpensiveAddButton re-rendu') // ← Toujours appelé !
return <button onClick={() => onAdd('New item')}>Add Item</button>
})
useCallback
Syntaxe et Usage Basique
import { useCallback } from 'react'
function OptimizedParent() {
const [count, setCount] = useState(0)
const [items, setItems] = useState([])
const [name, setName] = useState('')
// ✅ SOLUTION - Fonction mémorisée stable
const addItem = useCallback((text) => {
setItems(prev => [...prev, { id: Date.now(), text, completed: false }])
}, []) // ← Pas de dépendances = fonction stable
// ✅ SOLUTION - Fonction mémorisée stable
const handleClick = useCallback((id) => {
console.log('Item clicked:', id)
// Logique qui n'a pas de dépendances externes
}, [])
// ✅ SOLUTION - Fonction avec dépendances
const addItemWithPrefix = useCallback((text) => {
const prefix = name ? `[${name}] ` : ''
setItems(prev => [...prev, {
id: Date.now(),
text: prefix + text,
completed: false
}])
}, [name]) // ← Se recrée seulement si name change
// ✅ SOLUTION - Event handler optimisé
const handleCountChange = useCallback((increment) => {
setCount(prev => prev + increment)
}, [])
// ✅ SOLUTION - Fonction de suppression
const removeItem = useCallback((id) => {
setItems(prev => prev.filter(item => item.id !== id))
}, [])
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1 (non-optimisé)</button>
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="Préfixe..."
/>
{/* Maintenant ces composants ne re-render que si nécessaire ! */}
<OptimizedButton onClick={handleCountChange} increment={1}>+1</OptimizedButton>
<OptimizedButton onClick={handleCountChange} increment={5}>+5</OptimizedButton>
<OptimizedList
items={items}
onItemClick={handleClick}
onItemRemove={removeItem}
/>
<OptimizedAddForm
onAdd={addItem}
onAddWithPrefix={addItemWithPrefix}
/>
</div>
)
}
// Maintenant ces composants ne re-render que si leurs props changent vraiment !
const OptimizedButton = memo(function OptimizedButton({ onClick, increment, children }) {
console.log(`🔘 OptimizedButton (+${increment}) re-rendu`)
return (
<button onClick={() => onClick(increment)} style={{ margin: '5px' }}>
{children}
</button>
)
})
const OptimizedList = memo(function OptimizedList({ items, onItemClick, onItemRemove }) {
console.log('📝 OptimizedList re-rendu')
return (
<ul>
{items.map(item => (
<li key={item.id} style={{ display: 'flex', justifyContent: 'space-between' }}>
<span onClick={() => onItemClick(item.id)} style={{ cursor: 'pointer' }}>
{item.text}
</span>
<button onClick={() => onItemRemove(item.id)}>❌</button>
</li>
))}
</ul>
)
})
const OptimizedAddForm = memo(function OptimizedAddForm({ onAdd, onAddWithPrefix }) {
console.log('📝 OptimizedAddForm re-rendu')
const [input, setInput] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
if (input.trim()) {
onAdd(input.trim())
setInput('')
}
}
const handleSubmitWithPrefix = (e) => {
e.preventDefault()
if (input.trim()) {
onAddWithPrefix(input.trim())
setInput('')
}
}
return (
<form>
<input
value={input}
onChange={e => setInput(e.target.value)}
placeholder="Nouvel élément..."
/>
<button type="submit" onClick={handleSubmit}>Ajouter</button>
<button type="button" onClick={handleSubmitWithPrefix}>Avec préfixe</button>
</form>
)
})