Aller au contenu principal

Remonter l'état

Quand plusieurs composants ont besoin de la même information, elle vit chez leur parent commun.

Objectifs de la séance​

  • comprendre le principe du flux de données de React (de haut en bas) ;
  • remonter un état dans le composant parent le plus proche ;
  • faire parler un enfant à son parent avec une fonction transmise en prop (onTerminer, onDemarrer) ;
  • modéliser l'écran affiché avec une union d'objets ;
  • gérer le cas d'un niveau sans question.

Petite vérification​

Avant de commencer, vérifiez si vous savez...

  1. dans le formulaire, envoyer un pseudo de 2 lettres et lire le message d'erreur
  2. envoyer le pseudo Alice et lire ce que la console du navigateur (touche F12) affiche
  3. dire pourquoi la fonction versNiveau est préférée à evenement.target.value as Niveau
📌 Réponse attendue

Notions théoriques​

Un flux de données qui descend​

Dans React, les données circulent de haut en bas : un parent transmet des données à ses enfants avec des props. Un enfant ne peut pas modifier directement l'état de son parent.

App ← possède l'information « quel écran afficher »
├── FormulaireDemarrage ← reçoit onDemarrer
├── Partie ← reçoit les questions et onTerminer
└── Resultat ← reçoit le score et onRejouer

Remonter l'état​

Dans notre quiz, trois composants (le formulaire, la partie, le résultat) se succèdent à l'écran, et chacun a besoin d'une information venue du précédent : le pseudo, les questions du niveau choisi, le score. Aucun ne peut la stocker pour les autres.

La solution est de remonter l'état : on le place dans leur parent commun, ici App. Les enfants lui envoient les informations, il les redistribue.

L'enfant prévient le parent : les fonctions en props​

Pour qu'un enfant « parle » à son parent, le parent lui transmet une fonction (un callback) que l'enfant appellera au bon moment. Par convention, ces props commencent par on :

// Enfant : il appelle la fonction reçue
type FormulaireDemarrageProps = {
onDemarrer: (pseudo: string, niveau: Niveau) => void;
};

// Parent : il fournit la fonction
<FormulaireDemarrage onDemarrer={demarrer} />

Le type (pseudo: string, niveau: Niveau) => void décrit la fonction attendue : deux paramètres, aucun retour.

Modéliser l'écran affiché​

Un seul écran est visible à la fois, et chaque écran a besoin de ses propres données. Une union d'objets (comme dans la partie 6 du cours JavaScript) l'exprime parfaitement :

type Ecran =
| { nom: "accueil" }
| { nom: "partie"; pseudo: string; questions: Question[] }
| { nom: "resultat"; pseudo: string; score: number; total: number };

const [ecran, setEcran] = useState<Ecran>({ nom: "accueil" });

Impossible d'afficher le résultat sans score : TypeScript vérifie que chaque écran a bien les données nécessaires.

Afficher le bon écran​

{ecran.nom === "accueil" ? <FormulaireDemarrage onDemarrer={demarrer} /> : null}
{ecran.nom === "partie" ? (
<Partie questions={ecran.questions} onTerminer={...} />
) : null}

Dans le bloc ecran.nom === "partie", TypeScript sait que ecran.questions existe (il affine le type).

Donner un état initial à un enfant, ou le réinitialiser​

Un composant qui disparaît de l'écran perd son état. Quand Partie est remplacé par Resultat, puis de nouveau affiché, il redémarre avec un état neuf (indice 0, score 0). On n'a donc plus besoin de fonction recommencer : changer d'écran suffit.

Bonne pratique - Ne remonter que ce qui est partagé

Remontez un état seulement quand plusieurs composants en ont besoin. Le choix de la réponse (choix) ne concerne que Partie : il reste dans Partie.

Le niveau sans question​

Notre jeu de données contient trois questions : deux de niveau 1, une de niveau 2, aucune de niveau 3. Si le joueur choisit le niveau 3, la liste filtrée est vide. Le composant Partie doit le gérer :

if (question === undefined) {
return (
<section className="carte">
<p>Aucune question disponible pour ce niveau.</p>
</section>
);
}

Un code robuste prévoit les cas « vides » dès le début.


Test de mémorisation/compréhension​


Où faut-il placer un état dont ont besoin plusieurs composants frères ?


Comment un composant enfant informe-t-il son parent ?


Quel est le type de la prop onTerminer qui reçoit un score et ne renvoie rien ?


Que devient l'état d'un composant lorsqu'il disparaît de l'écran ?


Quel avantage offre le type Ecran (union d'objets) ?


Pourquoi le composant Partie gère-t-il le cas d'une liste de questions vide ?



TP pour réfléchir et résoudre des problèmes​

Votre mission : relier le formulaire, la partie et le résultat, avec App comme chef d'orchestre.

Étape 1 — Partie prévient quand elle est finie​

Modifiez Partie : elle reçoit une prop onTerminer et l'appelle avec le score à la fin de la dernière question (et non plus un écran « Partie terminée » interne).


Bonne pratique - Pas d'appel de callback pendant le rendu

onTerminer(score) est appelé dans une fonction d'événement (suivante, déclenchée par un clic), jamais directement dans le corps du composant : appeler un callback pendant l'affichage provoquerait des mises à jour en boucle.

Étape 2 — Le composant Resultat​

Créez src/components/Resultat.tsx, qui affiche le bilan et propose de rejouer.


Bonne pratique - Composant d'affichage pur

Resultat ne possède aucun état : il affiche ses props. Un composant sans état est le plus simple à comprendre et à tester.

Étape 3 — Le type Ecran dans App​

Dans App.tsx, déclarez le type Ecran et l'état qui désigne l'écran affiché.


Bonne pratique - Un seul état pour l'écran

Trois booléens (afficherAccueil, afficherPartie, afficherResultat) pourraient se contredire. Un seul état ecran garantit qu'un seul écran est affiché.

Étape 4 — Démarrer une partie​

Écrivez la fonction demarrer : elle filtre les questions du niveau choisi et passe à l'écran de la partie.


Bonne pratique - Calculer une fois, stocker le résultat

On filtre une seule fois, au moment du démarrage, et l'écran partie embarque ses questions. On évite de refaire le filtre à chaque affichage.

Étape 5 — Afficher le bon écran​

Complétez le return de App pour relier les trois composants.


Bonne pratique - Tester tous les parcours

Jouez les trois niveaux : le niveau 1 (2 questions), le niveau 2 (1 question) et le niveau 3 (aucune question : le message « Aucune question disponible pour ce niveau. » doit s'afficher).

📌 Une solution

Ce qu'il faut retenir​

NotionRésumé
Flux de donnéesLes données descendent par les props, les événements remontent par des fonctions.
Remonter l'étatPlacer l'état dans le parent commun des composants qui en ont besoin.
Prop onXxxUne fonction transmise à l'enfant, qu'il appelle pour prévenir son parent.
Union d'objetsEcran décrit un seul écran à la fois, avec ses propres données.
État localUn état qui ne concerne qu'un composant (choix) reste dans ce composant.
Cas videToujours prévoir un message quand il n'y a rien à afficher.

Aperçu de la prochaine séance​

Partie utilise trois états qui évoluent ensemble : changer l'un sans l'autre créerait des incohérences. Dans la prochaine séance, vous allez les regrouper avec useReducer : une seule fonction, écrite à part et testable, décide de tous les changements.