Contactez-nous

Lifting state up (Remonter l'état) : Partage d'état entre composants

Apprenez le pattern 'Lifting State Up' (remonter l'état) en React pour partager un état commun entre plusieurs composants en le plaçant dans leur ancêtre commun.

Le défi du partage d'état entre composants

Nous savons maintenant que les composants peuvent avoir leur propre état local pour gérer des données internes qui changent. Mais que se passe-t-il lorsque plusieurs composants ont besoin de refléter les mêmes données changeantes ? Ou lorsqu'un composant enfant doit pouvoir modifier une donnée qui affecte l'affichage ou le comportement de son parent ou d'autres composants frères (siblings) ?

Imaginez deux composants `TemperatureInput`, un pour Celsius et un pour Fahrenheit. Si l'utilisateur modifie la température dans l'un, l'autre doit se mettre à jour automatiquement pour afficher la valeur convertie. Ou bien, un composant `ListeDeProduits` et un composant `NombreDeProduits` qui doivent tous deux être au courant du nombre total de produits sélectionnés. Ces composants, même s'ils sont distincts, ont besoin d'accéder et potentiellement de modifier un état partagé.

Le problème est que l'état est local à un composant, et que les props ne circulent que du parent vers l'enfant. Comment faire communiquer ces composants ou synchroniser leur état ?

La solution React : Remonter l'état ('Lifting State Up')

La solution préconisée par React pour partager un état entre plusieurs composants est de remonter cet état (lift state up) vers leur ancêtre commun le plus proche dans l'arborescence des composants.

Au lieu que chaque composant enfant maintienne sa propre copie locale de l'état partagé, l'état est déplacé vers le composant parent qui englobe tous les composants ayant besoin de cette information. Ce parent devient alors la "source unique de vérité" (single source of truth) pour cet état.

Le parent gère l'état (avec `useState` par exemple) et passe ensuite :

  • La valeur de l'état en tant que prop aux composants enfants qui ont besoin de l'afficher ou de l'utiliser.
  • La fonction de mise à jour de l'état (ou une fonction qui l'appelle) en tant que prop aux composants enfants qui ont besoin de modifier cet état.

Ainsi, bien que l'état réside dans le parent, les enfants peuvent le lire (via les props) et demander sa modification (en appelant la fonction passée en prop). Lorsque l'état du parent change, il se re-rend, passant les nouvelles props à ses enfants, qui se mettent à jour à leur tour. Cela assure la synchronisation.

Mise en oeuvre : Etapes et exemple (Convertisseur de Température)

Les étapes pour remonter l'état sont généralement :

  1. Identifier les composants qui ont besoin de l'état partagé.
  2. Trouver leur ancêtre commun le plus proche dans l'arbre des composants.
  3. Déplacer la déclaration de l'état (`useState`) de l'enfant (ou des enfants) vers cet ancêtre commun.
  4. Passer la valeur de l'état de l'ancêtre aux enfants concernés via une prop.
  5. Passer la fonction de mise à jour de l'état (ou une fonction qui l'appelle) de l'ancêtre aux enfants qui doivent modifier l'état, via une autre prop (souvent nommée `onChange`, `onUpdate`, `set...`).
  6. Modifier les composants enfants pour qu'ils lisent la valeur depuis leurs props et appellent la fonction de mise à jour reçue en prop lorsqu'une modification est nécessaire.

Exemple : Convertisseur de Température

Supposons un composant `TemperatureInput` qui affiche une température et permet de la modifier :

// Composant enfant : TemperatureInput.js
import React from 'react';

const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};

function TemperatureInput({ temperature, scale, onTemperatureChange }) {
  const handleChange = (event) => {
    // Appelle la fonction passée en prop par le parent
    onTemperatureChange(event.target.value);
  };

  return (
    
Entrez la température en {scaleNames[scale]}: {/* Lit la température depuis les props */}
); } export default TemperatureInput;

Maintenant, le composant parent `Calculator` qui gère l'état et utilise deux `TemperatureInput` :

// Composant parent : Calculator.js
import React, { useState } from 'react';
import TemperatureInput from './TemperatureInput';

// Fonctions de conversion (exemple)
function toCelsius(fahrenheit) { /* ... conversion ... */ return (fahrenheit - 32) * 5 / 9; }
function toFahrenheit(celsius) { /* ... conversion ... */ return (celsius * 9 / 5) + 32; }
function tryConvert(temperature, convert) { /* ... gère entrée invalide et convertit ... */ 
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) { return ''; }
  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000;
  return rounded.toString();
}

function Calculator() {
  // 1. L'état est remonté ici, dans l'ancêtre commun
  const [temperature, setTemperature] = useState('');
  const [scale, setScale] = useState('c'); // Quelle échelle a été modifiée en dernier

  // 2. Fonctions de mise à jour qui modifient l'état du parent
  const handleCelsiusChange = (temp) => {
    setScale('c');
    setTemperature(temp);
  };

  const handleFahrenheitChange = (temp) => {
    setScale('f');
    setTemperature(temp);
  };

  // Calcul des valeurs pour les deux inputs basé sur l'état unique
  const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
  const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

  return (
    
{/* 3. Passe l'état et les fonctions de màj en props aux enfants */} {/* On pourrait aussi afficher un verdict basé sur 'celsius' ici */}
); } export default Calculator;

Dans cet exemple, `Calculator` est la source unique de vérité pour `temperature` et `scale`. Lorsque l'utilisateur tape dans l'un des `TemperatureInput`, la fonction `onTemperatureChange` correspondante (passée en prop) est appelée. Cette fonction met à jour l'état dans `Calculator`. `Calculator` se re-rend, recalcule les valeurs `celsius` et `fahrenheit`, et les repasse en props aux deux `TemperatureInput`, assurant ainsi leur synchronisation.

Avantages et inconvénients

Avantages :

  • Source unique de vérité : Simplifie le raisonnement sur l'état de l'application et évite les incohérences.
  • Prédictibilité : Le flux de données reste descendant, ce qui rend le débogage plus facile.
  • Communication indirecte : Permet aux enfants d'influencer l'état de l'application via les callbacks fournis par le parent.

Inconvénients potentiels :

  • Complexification du parent : Le composant ancêtre peut devenir plus complexe car il gère plus d'état et de logique.
  • "Prop Drilling" : Si l'ancêtre commun est très haut dans l'arbre, il peut être nécessaire de faire passer les props à travers de nombreux composants intermédiaires qui n'en ont pas besoin eux-mêmes, ce qui peut être fastidieux.

Conclusion : Le pattern standard pour la synchronisation d'état

Remonter l'état ("Lifting State Up") est le pattern fondamental et standard dans React pour gérer les situations où plusieurs composants ont besoin de partager ou de synchroniser un état. En centralisant l'état dans l'ancêtre commun le plus proche et en utilisant les props pour faire descendre la valeur et remonter les intentions de modification (via des fonctions callback), vous maintenez un flux de données unidirectionnel clair et assurez la cohérence de votre interface utilisateur.

Bien qu'il puisse introduire du "prop drilling" dans des cas complexes (pour lesquels des solutions comme l'API Context ou des bibliothèques de gestion d'état existent), c'est la première approche à considérer pour le partage d'état local entre quelques composants.