Apprends à partager l'état entre composants frères en le remontant vers leur parent commun, permettant ainsi un comportement coordonné dans ton arbre de composants.
Dans React, les données circulent vers le bas du parent vers l'enfant via les props. On appelle cela le flux de données unidirectionnel ou flux de données à sens unique. Mais que se passe-t-il quand deux composants frères doivent partager et réagir aux mêmes données ?
Considère un convertisseur de température avec deux champs de saisie — un pour les Celsius et un pour les Fahrenheit. Quand l'utilisateur tape dans le champ Celsius, l'affichage Fahrenheit doit se mettre à jour, et vice versa. Les deux composants ont besoin d'accéder à la même valeur de température.
Tu ne peux pas passer de données directement entre frères. À la place, tu remontes l'état partagé vers leur plus proche parent commun. Le parent détient l'état et le transmet aux deux enfants via les props.
Parent (détient l'état)
/ \
EnfantA EnfantB
(lit) (lit)Ce pattern s'appelle remonter l'état (lifting state up) et c'est l'un des patterns les plus fondamentaux dans React. Quand tu as besoin de synchroniser deux composants, cherche leur parent commun et déplace l'état là-bas.
Avant de remonter l'état (cassé) :
// Chaque composant a son propre état — ils ne peuvent pas communiquer
function CelsiusInput() {
const [celsius, setCelsius] = React.useState(0);
return <input value={celsius} onChange={...} />;
}
function FahrenheitDisplay() {
// Comment ce composant peut-il connaître la valeur Celsius ? Il ne peut pas !
return <p>???</p>;
}Après avoir remonté l'état (fonctionnel) :
function App() {
const [celsius, setCelsius] = React.useState(0);
return (
<>
<CelsiusInput celsius={celsius} onCelsiusChange={setCelsius} />
<FahrenheitDisplay celsius={celsius} />
</>
);
}Maintenant le parent possède les données et les deux enfants peuvent y accéder.
Voici le processus étape par étape pour remonter l'état :
Étape 1 : Identifier l'état partagé. Détermine quel état doit être partagé entre les composants. Dans notre exemple de température, c'est la valeur de température.
Étape 2 : Trouver le parent commun. Trouve le composant le plus proche dans l'arbre qui est un ancêtre de tous les composants ayant besoin de l'état partagé.
Étape 3 : Déplacer l'état vers le parent.
Retire l'état des composants enfants et ajoute-le au parent en utilisant useState.
Étape 4 : Passer l'état vers le bas via les props. Passe la valeur de l'état à chaque enfant qui a besoin de la lire.
Étape 5 : Passer la fonction de mise à jour comme prop. Passe la fonction de mise à jour (ou un gestionnaire qui l'appelle) à l'enfant qui doit modifier l'état.
Voici l'exemple complet :
function TemperatureConverter() {
// Étape 3 : L'état vit dans le parent
const [celsius, setCelsius] = React.useState(0);
return (
<div>
{/* Étapes 4 & 5 : Passer l'état et le modificateur vers le bas */}
<CelsiusInput
value={celsius}
onChange={setCelsius}
/>
<FahrenheitDisplay celsius={celsius} />
</div>
);
}
function CelsiusInput({ value, onChange }) {
return (
<label>
Celsius:
<input
type="number"
value={value}
onChange={(e) => onChange(Number(e.target.value))}
/>
</label>
);
}
function FahrenheitDisplay({ celsius }) {
const fahrenheit = celsius * 9 / 5 + 32;
return <p>{fahrenheit.toFixed(1)}°F</p>;
}Le CelsiusInput ne possède pas l'état de température — il reçoit la valeur et appelle onChange quand l'utilisateur tape. Le parent reçoit la mise à jour, change son état, et les deux enfants se re-rendent avec la nouvelle valeur.
Quand un composant enfant doit modifier l'état du parent, le parent passe une fonction callback. C'est ainsi que les données circulent vers le haut dans React — via des callbacks.
function Parent() {
const [items, setItems] = React.useState(['Item 1', 'Item 2']);
function addItem(text) {
setItems([...items, text]);
}
function removeItem(index) {
setItems(items.filter((_, i) => i !== index));
}
return (
<div>
<AddItemForm onAdd={addItem} />
<ItemList items={items} onRemove={removeItem} />
</div>
);
}
function AddItemForm({ onAdd }) {
const [text, setText] = React.useState('');
function handleSubmit(e) {
e.preventDefault();
if (text.trim()) {
onAdd(text); // Appeler la fonction du parent
setText('');
}
}
return (
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button type="submit">Ajouter</button>
</form>
);
}
function ItemList({ items, onRemove }) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>
{item}
<button onClick={() => onRemove(index)}>Retirer</button>
</li>
))}
</ul>
);
}Points clés :
onAdd, onRemove) fourni par le parent.Cela garde les composants enfants réutilisables. Ils fonctionnent avec n'importe quel parent qui fournit les props callback attendues.
Le principe derrière la remontée d'état est la source unique de vérité. Pour toute donnée qui change dans ton application, il devrait y avoir exactement un composant qui la "possède" dans son état. Tous les autres composants qui ont besoin de ces données devraient les recevoir via les props.
Pourquoi est-ce important ?
Pas d'incohérence de données. Si deux composants ont chacun leur propre copie des mêmes données, elles peuvent se désynchroniser. Avec une source unique, il n'y a qu'une seule valeur et elle est toujours cohérente.
Débogage plus facile. Quand quelque chose ne va pas, tu n'as besoin de regarder qu'à un seul endroit pour trouver la source des données.
Mises à jour prévisibles. Tous les changements d'état passent par un seul chemin, rendant le flux de données facile à tracer.
Exemple d'état dupliqué (mauvais) :
// Mauvais : les deux enfants ont leur propre copie de "name"
function Parent() {
return (
<>
<NameInput /> {/* A son propre état name */}
<Greeting /> {/* A son propre état name */}
</>
);
}
// Si NameInput se met à jour, Greeting ne le sait pas !Exemple de source unique de vérité (bon) :
// Bon : le parent possède l'état, les enfants reçoivent via les props
function Parent() {
const [name, setName] = React.useState('');
return (
<>
<NameInput name={name} onNameChange={setName} />
<Greeting name={name} />
</>
);
}
// Les deux enfants affichent toujours le même nomComment identifier où l'état devrait vivre :
À mesure que ton application grandit, les composants doivent communiquer de différentes façons. Voici un résumé des principaux patterns :
Parent vers enfant (props) : Le pattern le plus simple. Le parent passe les données vers le bas via les props.
<UserCard name={user.name} email={user.email} />Enfant vers parent (callbacks) : Le parent passe une fonction comme prop. L'enfant l'appelle pour envoyer des données vers le haut.
// Parent
function Parent() {
function handleSelect(item) {
console.log('Sélectionné:', item);
}
return <Menu onSelect={handleSelect} />;
}
// Enfant
function Menu({ onSelect }) {
return <button onClick={() => onSelect('about')}>À propos</button>;
}Frère vers frère (remontée d'état) : Remonte l'état partagé vers le parent commun et passe-le aux deux frères.
function Parent() {
const [selected, setSelected] = React.useState(null);
return (
<>
<Sidebar items={items} onSelect={setSelected} />
<Detail selected={selected} />
</>
);
}Quand la remontée d'état devient lourde : Si tu te retrouves à passer des props à travers de nombreuses couches de composants qui n'utilisent pas eux-mêmes les données (appelé prop drilling), il est peut-être temps de considérer :
Ce sont des sujets plus avancés, mais la fondation est toujours la même : un composant possède l'état, et les autres composants y accèdent via une interface bien définie.
<div id="root"></div>
<script src="https://unpkg.com/react@19/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const { useState } = React;
function CelsiusInput({ value, onChange }) {
return (
<label>
Celsius:
<input
type="number"
value={value}
onChange={(e) => onChange(Number(e.target.value))}
/>
</label>
);
}
function FahrenheitDisplay({ celsius }) {
const fahrenheit = (celsius * 9) / 5 + 32;
return <p>Fahrenheit: {fahrenheit.toFixed(1)}°F</p>;
}
function App() {
const [celsius, setCelsius] = useState(0);
return (
<div>
<h2>Convertisseur de Température</h2>
<CelsiusInput value={celsius} onChange={setCelsius} />
<FahrenheitDisplay celsius={celsius} />
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>Quand deux composants frères doivent partager un état, où cet état devrait-il vivre ?