DEV Community

Mathias PAYEN
Mathias PAYEN

Posted on • Updated on

Comprendre et utiliser useCallback - Tuto

Il y a quelques semaines, j’ai réaliser un test technique pour un poste de développeur react Junior. Le test proposait 2 exercices dont un très simple:
Réaliser un composant comprenant 5 checkboxes. 4 checkboxes normal, et 1 permettant de sélectionner ou de désélectionner tous les autres.
Point important précisé dans l’énoncé « Prenez votre temps ». Chose que je n’ai pas faite.
Je me suis précipité et me suis fait recaler pour la raison suivante: Code non performant !

Je vous propose donc dans cet article de voir, par un exemple très simple, comment améliorer ce type de composant avec les méthodes useCallback et memo proposées par React qui vont permettre d’éviter les rendus inutiles.


  1. Mise en place des composants

    a - On crée notre composant Checkboxe. Celui-ci reçoit des props. Un permettra de gérer son état checked, id pour matcher avec le label, et la fonction handleChange sur l’évènement onChange de l’input.

On n'oublie pas les PropTypes ;)

import React from 'react';
import PropTypes from 'prop-types';

const Checkboxe = ({
    label,
    checked,
    handleChange,
    id,
}) => {

    console.log('id : ' + id);

    return (  
        <div>
          <input 
            type="checkbox" 
            id={id} 
            name={id} 
            checked={checked} 
            onChange={handleChange}
        />
          <label htmlFor={id}>
              {label}
          </label>
        </div>
    );
}

Checkboxe.defaultProps = {
    label: 'item 1',
    id: 'scales',
    checked: true,
    handleChange: () => {},
    array: [],
}

Checkboxe.propTypes = {
    label: PropTypes.string,
    id: PropTypes.string,
    checked: PropTypes.bool,
    handleChange: PropTypes.func,
    array: PropTypes.array,
}

export default Checkboxe;
Enter fullscreen mode Exit fullscreen mode

b - On crée notre composant parent, qui va gérer les états des checkboxes. On va appeler dans celui ci, 3 Checkboxes (pour faire très simple)

import React from 'react';
import Checkboxe from './Checkboxe';

const CheckForDev = () => {

    return (  
        <div className="container">

            <div className="checkboxes-container">
                <Checkboxe 
                    label="Item 1"
                    id="checkboxe1"
                    checked={}
                    handleChange={}
                />
                <Checkboxe 
                    label="Item 2"
                    id="checkboxe2"
                    checked={}
                    handleChange={}

                />
                <Checkboxe 
                    label="Item 3"
                    id="checkboxe3"
                    checked={}
                    handleChange={}                
                />
            </div>
        </div>

    );
}

export default CheckForDev;
Enter fullscreen mode Exit fullscreen mode

c - On déclare un state pour chaque Checkboxe

    const [check1, setCheck1] = useState(false);
    const [check2, setCheck2] = useState(false);
    const [check3, setCheck3] = useState(false);
Enter fullscreen mode Exit fullscreen mode

d - On fait passer en props de chaque checkboxe son state ainsi sa fonction de changement d’état.

<Checkboxe 
  label="Item 1"
  id="checkboxe1"
  checked={check1}
  handleChange={() => setCheck1(prev => !prev)}
/>
<Checkboxe 
  label="Item 2"
  id="checkboxe2"
  checked={check2}
  handleChange={() => setCheck2(prev => !prev)}
 />
<Checkboxe 
  label="Item 3"
  id="checkboxe3"
  checked={check3}
  handleChange={() => setCheck3(prev => !prev)}
/>
Enter fullscreen mode Exit fullscreen mode

On peut maintenant jouir pleinement des checkboxes qui fonctionne.
C’est génial !!

C’est plus ou moins avec ce code que je me suis fait recaler du poste… ( Tu m'étonnes !!! )
Pourquoi ??

Pour répondre à cette question, dans le composant checkboxe loguons le props id pour bien voir quel composant est rendu.

console.log('id : ' + id);
Enter fullscreen mode Exit fullscreen mode

Lors du premier rendu, quand l’app se monte, on peut voir en console 3 logs. Un pour chaque composant.

Image description

Quand on clique sur un Checkboxe, on voit que les 3 inputs sont re-rendus….

Image description

Or, il n'y a qu'une seul valeur qui a changé. Il y a donc 2 composants qui sont re-rendus inutilement.
En effet, une valeur d’état du composant gérant le state des checkboxes change, du coup c’est tout ce composant qui est re-rendu.

Pour des soucis de performances nous pouvons éviter cela, et permettre, dans notre exemple, de re-rendre seulement un checkboxe lors d’un changement d’état de ces derniers.

Comment ?

Grâce au méthodes useCallback et mémo de React.
useCallback va permettre de mémoïser les fonctions et de recréer une référence sur la stack seulement si nécessaire…

C’est parti !

2. Amélioration des composants avec les méthodes useCallback et memo

On crée une fonction pour chaque Checkboxe qui renverra une fonction de rappel mémoïsée. Celle-ci changera seulement si une des entrées change.

Ce qui veut dire par exemple que le Checkboxe numéro 1 ne sera re-rendu seulement si le state check1 change de valeur.

    const handleCheck1 = useCallback(() => {
        setCheck1(prev => !prev);
    }, []);

    const handleCheck2 = useCallback(() => {
        setCheck2(prev => !prev);
    }, []);

    const handleCheck3 = useCallback(() => {
        setCheck3(prev => !prev);
    }, []);
Enter fullscreen mode Exit fullscreen mode

Le props handleChange des composants CheckBoxe devient

  handleChange={handleCheck1}
  handleChange={handleCheck2}
  handleChange={handleCheck3}
Enter fullscreen mode Exit fullscreen mode

Testez.
Vous constatez en console que rien n'a changé.

Pour que cela fonctionne nous devons dire au composant checkboxe : «Mec, recrée une référence seulement si tu as un props qui change de valeur.»
Pour ca, on enveloppe Checkboxe avec React.memo de cette manière

const Checkboxe = React.memo(({
    label,
    checked,
    handleChange,
    id,
    classes
}) => {

    console.log('id : ' + id);

    return (  
        <div className={classes} >
          <input 
            type="checkbox" 
            id={id} 
            name={id} 
            checked={checked} 
            onChange={handleChange}
        />
          <label htmlFor={id}>
              {label}
          </label>
        </div>
    );
})
Enter fullscreen mode Exit fullscreen mode

On a bien 3 logs lors du montage de l’app.
Puis seulement le Checkboxe sur lequel on a cliqué qui est re-rendu.

Grâce à ce petit exemple, nous pouvons rapidement comprendre l’impact sur les performances d’une application plus conséquente.

J'ai mis ici [https://codesandbox.io/s/elegant-brown-cexzd?file=/src/styles.css] le composant sans et avec mémoïsation pour comparer les rendus facilement !

Oldest comments (0)