Aller au contenu principal

L'écran de résultat

Afficher le score dans un écran dédié, et empêcher le bouton « retour » de ramener le joueur dans une partie déjà terminée.

Objectifs de la séance​

  • comprendre la différence entre router.push et router.replace ;
  • utiliser router.dismissTo("/") pour revenir à l'accueil ;
  • transmettre un résultat dans l'adresse et le valider (cohérence du score et du total) ;
  • simplifier le reducer et le chronomètre maintenant que « Rejouer » est une navigation ;
  • fusionner la branche du tutoriel avec une Pull Request.

Petite vérification​

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

  1. taper dans le navigateur l'adresse /partie?theme=pirates&niveau=1&nombre=3 et lire ce qui s'affiche
  2. taper l'adresse /partie?theme=tous&niveau=tous&nombre=999 et lire ce qui s'affiche
  3. exécuter npm test et lire le nombre de tests réussis
📌 Réponse attendue

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

Votre mission : à la fin d'une partie, ouvrir un écran resultat qui affiche le score et le temps, avec trois boutons (rejouer avec les mêmes réglages, nouveau quiz, accueil). Vous êtes sur la branche feature/navigation.

Étape 1 — Simplifier le reducer​

Le bouton « Rejouer » ne sera plus dans l'écran de partie : rejouer, c'est ouvrir une nouvelle partie. L'action RECOMMENCER n'a donc plus d'utilité. Dans src/logic/partieReducer.ts, supprimez-la du type des actions, le case "RECOMMENCER" du reducer, et le test correspondant s'il existe.


Bonne pratique - Supprimer le code devenu inutile

Une action jamais envoyée est du code mort : elle fait croire qu'une fonctionnalité existe et elle reste à maintenir. Supprimez-la dès qu'elle ne sert plus.

Étape 2 — Simplifier le chronomètre​

Sans « Rejouer » dans la partie, plus besoin de remettreAZero : un nouvel écran de partie redémarre à zéro tout seul. Le hook renvoie directement le nombre de secondes.

Supprimez aussi la fonction remettreAZero du hook. L'appel devient const secondes = useChronometre(...).


Bonne pratique - Une API minimale

Un hook doit exposer uniquement ce dont ses utilisateurs ont besoin. Moins de fonctions exposées, moins de façons de s'en servir de travers.

Étape 3 — Lire un entier dans l'adresse​

Créez src/logic/parametresResultat.ts. Le résultat est transmis par trois paramètres (score, total, secondes), à lire et valider comme à la séance précédente.


Bonne pratique - Réutiliser le même réflexe

La même démarche qu'à la séance précédente : vérifier le format avec l'expression régulière, puis la valeur. Une fois le réflexe acquis, valider une donnée devient rapide.

Étape 4 — Vérifier la cohérence du résultat​

Un score de 6 sur 5 questions n'a pas de sens : une adresse falsifiée pourrait afficher un score impossible. On vérifie donc aussi la cohérence.


Bonne pratique - Valider le sens, pas seulement la forme

Un nombre peut avoir le bon format et être absurde. Les règles métier (score ≤ total) font partie de la validation.

Étape 5 — Tester le résultat​

Créez tests/parametresResultat.test.ts.

Lancez npm test : tests 39 et pass 39.


Bonne pratique - Garder les tests à jour

Si vous aviez un test sur RECOMMENCER, il a été supprimé à l'étape 1 : un test qui vérifie une fonctionnalité disparue fait échouer npm test pour rien.

Étape 6 — Ouvrir le résultat à la fin de la partie​

Dans src/components/PartieEnCours.tsx, ajoutez la fonction afficherLeResultat. Elle remplace l'écran de partie par l'écran de résultat, en lui transmettant le score, le total, le temps et les réglages.

Supprimez de ce fichier l'écran « Quiz terminé ! » (le if (etat.statut === "terminee")), la fonction rejouer et le style score, devenus inutiles.


Bonne pratique - replace pour ne pas laisser de trace

Avec push, le bouton retour ramènerait le joueur dans la partie terminée, où le reducer ignore toute réponse : un écran bloqué. Avec replace, la partie disparaît de la pile.

Étape 7 — Brancher le bouton « Voir le résultat »​

Sur la dernière question, le bouton appelle afficherLeResultat ; sinon il envoie QUESTION_SUIVANTE.


Bonne pratique - Passer une fonction sans l'appeler

On écrit afficherLeResultat (sans parenthèses) : on donne la fonction à React pour qu'il l'appelle au clic. Avec des parenthèses, elle serait appelée tout de suite, pendant l'affichage.

Étape 8 — L'écran de résultat​

Créez src/app/resultat.tsx. Il lit les paramètres, les valide (résultat et réglages), et affiche trois boutons.

  • Rejouer (mêmes réglages) : router.replace vers /partie avec les mêmes paramètres ;
  • Nouveau quiz : router.replace("/generateur") ;
  • Accueil : router.dismissTo("/") retire tous les écrans empilés jusqu'à l'accueil.

Bonne pratique - Une erreur claire si l'adresse est falsifiée

Si lireResultat ou lireConfigPartie renvoie null, l'écran affiche « Résultat introuvable. » avec un bouton vers l'accueil, comme l'écran de partie.

Étape 9 — Déclarer l'écran dans la pile​

Jouez une partie complète : à la fin, l'écran de résultat s'affiche. Appuyez sur le bouton retour : vous ne revenez plus dans la partie terminée.


Bonne pratique - Tester le chemin du bouton retour

Beaucoup de bugs de navigation n'apparaissent qu'avec le bouton retour. Prenez l'habitude de l'essayer à chaque nouvel écran.

Étape 10 — Enregistrer votre travail​


Bonne pratique - Pousser avant la Pull Request

La Pull Request se crée à partir de la branche publiée sur GitHub : le dernier git push doit avoir été fait.

Étape 11 — Fusionner la branche du tutoriel​

Comme à la fin du tutoriel précédent :

  1. sur GitHub, cliquez sur Compare & pull request pour la branche feature/navigation ;
  2. donnez-lui un titre (par exemple « Tutoriel 5 : navigation avec Expo Router »), relisez Files changed, puis Create pull request ;
  3. cliquez sur Merge pull request, puis Confirm merge ;
  4. dans le terminal, retournez sur main et récupérez la version fusionnée.

Bonne pratique - Nettoyer les branches fusionnées

Une fois la Pull Request fusionnée, la branche locale ne sert plus : git branch -d feature/navigation la supprime. Elle reste dans l'historique de main.

📌 Une solution

Notions théoriques​

push ou replace ?​

MéthodeEffet sur la pile d'écransBouton retour
router.push("/x")ajoute l'écran x au-dessusramène à l'écran précédent
router.replace("/x")remplace l'écran actuel par xramène à l'écran d'avant l'écran remplacé

Avec push, à la fin d'une partie :

accueil > generateur > partie (terminée) > resultat
^ le bouton retour ramène ici : un écran bloqué !

Avec replace :

accueil > generateur > resultat
^ le bouton retour ramène au générateur

La règle : on utilise replace quand l'écran quitté n'a plus de raison d'être revisité (une partie terminée, un formulaire envoyé, une page de connexion une fois connecté).

dismissTo : revenir à un écran de la pile​

router.dismissTo("/") retire de la pile tous les écrans au-dessus de l'accueil et revient dessus. C'est le bon choix pour un bouton « Accueil » : après une longue navigation, la pile n'est pas remplie de doublons.

Transmettre un résultat dans l'adresse​

Pour cette séance, le résultat de la partie voyage dans l'adresse (/resultat?score=4&total=5&secondes=42&...). C'est simple, et l'écran de résultat peut être rechargé sans perdre l'information. Mais il y a une conséquence : n'importe qui peut fabriquer cette adresse et afficher « 5 / 5 en 1 seconde ».

Un score dans une adresse n'est pas digne de confiance

Ici, ce n'est pas grave : le score ne sert qu'à l'affichage, il n'est enregistré nulle part. Mais le jour où le classement enregistrera des scores, le serveur devra calculer le score lui-même à partir des réponses. Ce sera le sujet du tutoriel sur la sécurité et Supabase. En attendant, on valide au moins la cohérence : un score ne dépasse jamais le total.

Pourquoi supprimer RECOMMENCER ?​

Rejouer revient désormais à ouvrir une nouvelle partie (router.replace vers /partie). L'écran de partie est recréé avec un état tout neuf : nouvelles questions, chronomètre à zéro. Plus besoin de l'action RECOMMENCER, ni de remettreAZero. En supprimant ce qui ne sert plus, on simplifie le code et on réduit les risques d'erreur.

Le chronomètre s'arrête-t-il ?​

Quand le joueur clique sur « Voir le résultat », l'écran de partie est remplacé : il disparaît. React exécute alors la fonction de nettoyage du chronomètre (clearInterval) : la minuterie est arrêtée (vous l'avez vu à la séance sur useEffect).


Test de mémorisation/compréhension​


Quelle est la différence entre router.push et router.replace ?


Pourquoi utilise-t-on router.replace pour passer de la partie au résultat ?


Que fait router.dismissTo("/") ?


Pourquoi lireResultat refuse-t-elle un score supérieur au total ?


Pourquoi l'action RECOMMENCER a-t-elle été supprimée du reducer ?


Pourquoi un score transmis dans l'adresse n'est-il pas digne de confiance ?



Exercices complémentaires​

Exercice A : une adresse de résultat falsifiée​

Tapez dans le navigateur /resultat?score=6&total=5&secondes=42&theme=tous&niveau=tous&nombre=5. Que s'affiche-t-il, et pourquoi ? Essayez ensuite avec score=5.

Exercice B : dessiner la pile​

Sur papier, dessinez la pile d'écrans après chacune de ces actions : ouvrir le générateur, générer un quiz, terminer la partie, appuyer sur « Nouveau quiz ». Combien d'écrans contient la pile à la fin ?


Ce qu'il faut retenir​

NotionRésumé
router.pushAjoute un écran à la pile ; le bouton retour le retire.
router.replaceRemplace l'écran actuel : à utiliser quand l'écran quitté n'a plus de raison d'être revisité.
router.dismissTo("/")Retire les écrans empilés au-dessus de l'accueil et revient dessus.
Résultat dans l'adressePratique, mais falsifiable : on valide le format et la cohérence (score ≤ total).
Code mortSupprimer ce qui ne sert plus (RECOMMENCER, remettreAZero).
Pull RequestLa branche feature/navigation est fusionnée dans main à la fin du tutoriel.

Aperçu de la prochaine séance​

Le tutoriel 5 est terminé : votre application a quatre écrans (accueil, générateur, partie, résultat) et valide tout ce qui vient de l'adresse. Mais les 27 questions sont écrites dans le code, et n'importe quel joueur peut les lire en ouvrant le code. Dans le tutoriel suivant, vous allez créer une vraie base de données avec Supabase, en commençant par modéliser les données (MCD et MLD).