DEV Community

Yacine C
Yacine C

Posted on • Edited on

React - Pourquoi mon component ne se re-render pas

Quand on utilise React, il n’y a rien de pire qu’un component qui ne se re-render pas correctement. En général on essaye de rajouter des useEffect un peu partout pour forcer le refresh du component et que “cela fonctionne”. Cependant cette approche ne résout pas le vrai problème qui est très souvent engendré par un problème d’immutabilité de state.

Qu’est ce que l’immutabilité ?

Pour comprendre le principe d’immutabilité, il faut bien comprendre la différence entre les valeurs primitives et les valeurs complexes en Javascript.

Les valeurs primitives représentent les données qui ne sont ni des objets, ni des méthodes et ni des propriétés Javascript. Ils regroupent les types suivants : number, string, boolean, bigint, null, undefined et symbol.

On dit que les valeurs primitives sont immutables car elles ne peuvent pas être modifiées. En effet, si on écrit:

let size = 3;
Enter fullscreen mode Exit fullscreen mode

La valeur 3 ne changera jamais. C’est important de ne pas confondre la primitive elle-même (ici 3) et la variable à laquelle on assigne la valeur (ici size), car on peut réassigner une nouvelle valeur à notre variable.

On peut alors se demander, que se passe-t-il lorsqu’on affecte une nouvelle valeur à notre variable ?

let size = 3;
size = 10;
Enter fullscreen mode Exit fullscreen mode

Lorsque l'on créé une variable, on lui alloue un espace mémoire où on stockera sa valeur, on parle alors de référence. Sur la première ligne, on créé une variable, une référence lui est donc attribuée**, puis on l’initialise avec la valeur 3.

Sur la seconde ligne, on change simplement la valeur contenue dans l’espace mémoire alloué à la variable size.

Entre la 1ère et la 2ème ligne, on ne change donc pas la référence de notre variable mais seulement sa valeur.

Le principe est le même pour les valeurs complexes :

let fruits = ["apple", "banana", "strawberry"]
fruits.push("kiwi");
Enter fullscreen mode Exit fullscreen mode

On commence d’abord par allouer un espace mémoire à notre variable fruits(référence), on lui affecte les valeurs “apple”, “banana” et “strawberry”.

Puis sur la seconde ligne on garde la même référence pour fruits, et on rajoute l’élément “kiwi” dans notre tableau.

Contrairement aux valeurs primitives, les valeurs complexes sont dites mutables. Elles ne sont pas fixes et peuvent être modifiées comme dans notre exemple avec le tableau.

Quel est le rapport avec nos components React 🤨

En React, on peut définir des states qui contiennent aussi bien des valeurs primitives que complexes :

const [firstName, setFirstName] = useState("Bob");
const [flower, setFlower] = useState({name: "tulip", color: "yellow" });
Enter fullscreen mode Exit fullscreen mode

Prenons un exemple, on va écrire un component avec un state age qui contient un number. On affiche ce state dans une balise <p> et un bouton qui permet d’incrémenter la valeur du state de 1.

const App = () => {
    const [age, setAge] = useState(32);

    function handleClick(e) {
        age = age + 1;
        setAge(age);
    }

    return (
        <div>
            <p>{age} ans</p>
            <button onClick={handleClick}>Incrémenter</button>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Ce code vous semble correct ? Pourtant il ne l’est pas ! Si vous testez, vous vous rendrez compte que votre component ne se re-render pas.

Le problème c’est que React considère qu’un state a été modifié seulement si l’on modifie sa référence. On appelle ça une comparaison superficielle.

La méthode setAge récupère donc la référence de la valeur qu’on lui passe.

Ici, on modifie la valeur de age : age = age + 1, donc aucun refresh n’est effectué.

Puis on assigne la même référence à notre variable age : setAge(age), donc aucun re-render n’est effectué.

Il n’y a donc aucun changement détecté par React.

Pour pallier à ce problème, on écrira plutôt ceci :

function handleClick(e) {
        setAge(age + 1);
}
Enter fullscreen mode Exit fullscreen mode

À la compilation, age + 1 va être calculé et stocké dans la mémoire. setAge récupèrera donc la référence qui contiendra le calcul de age + 1 (donc une valeur primitive). On dit alors que la valeur passée à setAge est immutable (car elle ne peut pas être modifiée).

Pour les valeurs primitives c’est donc assez simple, en revanche lorsqu’on utilise des valeurs complexes comme des objets ou des tableaux, comment faire pour passer une nouvelle référence à notre méthode ?

Un exemple vaut bien mieux que mille mots !

const App = () => {
    const [user, setUser] = useState({name: "Bob", age: 8});

    function handleClick(e) {
        user.age = user.age + 1;
        setUser(user);
    }

    return (
        <div>
            <p>{user.name} à {user.age} ans</p>
            <button onClick={handleClick}>Incrémenter</button>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Vous l’aurez deviné, ce code ne fonctionne pas. On retrouve le même problème que tout à l’heure, on modifie la valeur de la variable mais pas sa référence. Il n’y a donc pas de modification détectée par React et donc pas de re-render.

Pour résoudre ce problème, on va utiliser ce qu’on appelle la déstructuration avec l’opérateur spread

function handleClick(e) {
        setUser({...user, age: user.age + 1});
    }
Enter fullscreen mode Exit fullscreen mode

Lorsqu’on écrit {…user}, on crée un nouvel objet dans lequel on “déstructure” tous les champs contenus dans notre ancien objet user. Ce code est équivalent à : {name: user.name, age: user.age}

En revanche, on ne souhaite pas garder le même age, alors on redéfinit la propriété age juste après l’utilisation de …user

{...user, age: user.age + 1}
Enter fullscreen mode Exit fullscreen mode

Même chose pour vos tableaux, on peut aussi utiliser le spread operator de la même façon :

const App = () => {
    const [myArray, setMyArray] = useState([]);

    function handleClick(e) {
        const newElement = // some code...

        setMyArray([...myArray, newElement]); // On ajoute le nouvel element au tableau
    }

    return (
        <div>
            <p>{myArray.join(", ")}</p>
            <button onClick={handleClick}>Ajouter</button>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

En résumé

Si vous rencontrez un problème de re-render avec vos components, la meilleure approche sera d’abord investiguer du côté de vos states et de leur modification. Ce problème s’applique aussi à bien d’autres notions de React comme les contexts, les reducers etc.

Pour en savoir plus, n'hésitez pas à lire la documentation React

Top comments (0)