Riprendiamo la serie relativa agli hook per la memorizzazione dei dati messe a disposizione da React. In questo post trattiamo useCallback, che consente di memorizzare le funzioni. Ripartendo dall’esempio del post precedente in cui era stato implementato un filtro su una lista di utenti, sono state aggiunte due funzioni per l’aggiunta e la cancellazione di utenti nella lista iniziale.
import React from 'react';
import { v4 as uuidv4 } from 'uuid';
const App = () => {
console.log('Render: App');
const [users, setUsers] = React.useState([
{ id: 'a', name: 'Robin' },
{ id: 'b', name: 'Dennis' },
]);
const [text, setText] = React.useState('');
const handleText = (event) => {
setText(event.target.value);
};
const handleAddUser = () =>{
setUsers(users.concat({ id: uuidv4(), name: text }));
};
const handleRemove = (id) => {
console.log('Render: List');
setUsers(users.filter((user) => user.id !== id));
};
return (
<div>
<input type="text" value={text} onChange={handleText} />
<button type="button" onClick={handleAddUser}>
Add User
</button>
<List list={users} onRemove={handleRemove} />
</div>
);
};
const List = ({ list, onRemove }) => {
return (
<ul>
{list.map((item) => (
<ListItem key={item.id} item={item} onRemove={onRemove} />
))}
</ul>
);
};
const ListItem = ({ item, onRemove }) => {
console.log('Render: ListItem');
return (
<li>
{item.name}
<button type="button" onClick={() => onRemove(item.id)}>
Remove
</button>
</li>
);
};
export default App;Quello che si vuole evitare è che ogni componente venga re-renderizzato ad ogni variazione all’interno della casella di input.
Digitando un valore all’interno della casella di testo dovrebbe verificarsi un nuovo render solo di App, ma non dei suoi componenti figli che non dovrebbero essere interessati dalle modifiche al componente padre.
Per raggiungere questo obiettivo si può pensare di aggiungere ai componenti figli React.memo, nel seguente modo:
const List = React.memo(({ list, onRemove }) => {
console.log('Render: List');
return (
<ul>
{list.map((item) => (
<ListItem key={item.id} item={item} onRemove={onRemove} />
))}
</ul>
);
});
const ListItem = React.memo(({ item, onRemove }) => {
console.log('Render: ListItem');
return (
<li>
{item.name}
<button type="button" onClick={() => onRemove(item.id)}>
Remove
</button>
</li>
);
});Applicando React.memo ci si aspetta che i figli non vengano effettivamente rendirezzati ad ogni modifica della casella di input. Facendo un rapido test, si nota che non è cosi. Perchè?
Finchè nessun elemento viene aggiunto o rimosso nella lista tutto dovrebbe essere rimasto inalterato, anche se il componente App esegue nuovamente il rendering.
Quindi l’aggiornamento degli elementi figli è dovuto sicuramente alla callback onRemove().
Ogni volta che il componente App viene rendirazzato la funzione handleRemove viene ridefinta, e di conseguenza ri-eseguita.
Passando questo nuovo gestore di callback come prop al componente List, viene rilevata la prop come modificata rispetto al render precedente .
Ecco perché viene attivato il re-render per i componenti List e ListItem.
Si potrebbe ipotizzare di utilizzare qualcosa che memorizzi la funzione. React mette a disposizione useCallback() che fa proprio questo. Il codice diventa:
const App = ( ) => {
...
const handleRemove = React.useCallback (
(id) => setUsers (users.filter((user)=>user.id !== id )),
[ users ]
) ;
...
} ;Utilizzando useCallback la funzione viene ridefinita solo quando si verifica una variazione dell’array delle dipendenze, in questo caso di users. Quindi ad ogni variazione di users la funzione verrà modificata.