React - Pourquoi mon component ne se re-render pas
Tags: webdev, beginners, react, javascript
Published: 2023-04-09 16:51:00

Quand on utilise [React](https://react.dev/), 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:

```javascript
let size = 3;
```

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 ?

```javascript
let size = 3;
size = 10;
```

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 :

```javascript
let fruits = ["apple", "banana", "strawberry"]
fruits.push("kiwi");
```

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 :

```javascript
const [firstName, setFirstName] = useState("Bob");
const [flower, setFlower] = useState({name: "tulip", color: "yellow" });
```

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.

```javascript
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>
	)
}
```

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 :

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

À 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 !

```javascript
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>
	)
}
```

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 `…`

```javascript
function handleClick(e) {
		setUser({...user, age: user.age + 1});
	}
```

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`

```javascript
{...user, age: user.age + 1}
```

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

```javascript
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>
	)
}
```

## 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](https://react.dev/learn/updating-objects-in-state)