Charger les thèmes : chargement, erreur, succès
Remplacer la liste de thèmes écrite dans le code par celle de la base Supabase, en gérant proprement l'attente et les pannes.
Objectifs de la séance
- écrire une fonction asynchrone avec
async,awaitettry/catch; - représenter les trois états d'un chargement (
chargement,erreur,succes) avec un type TypeScript ; - afficher un indicateur d'attente et un message d'erreur avec un bouton Réessayer ;
- ranger les appels à Supabase dans un service et vérifier les données reçues avant de les utiliser.
Petite vérification
Avant de commencer, vérifiez si vous savez...
- lancer l'application dans le navigateur (
npx expo start, touchew) et lire le message affiché par l'accueil sous la présentation - afficher dans un terminal le nom de la branche Git courante, puis les trois derniers commits, et lire le message du plus récent
- citer le préfixe obligatoire du nom d'une variable d'environnement lisible dans le code d'une application Expo
📌 Réponse attendue
TP pour réfléchir et résoudre des problèmes
Jusqu'ici, l'écran « Générateur de quiz » affichait les thèmes écrits dans le code. Votre mission : les lire dans Supabase, en prévoyant les trois situations possibles : la réponse n'est pas encore arrivée, la requête a échoué, la requête a réussi. Les explications détaillées suivent dans les Notions théoriques.
Étape 1 — Créer la branche du tutoriel
Repartez de main à jour (la Pull Request du tutoriel 6 a été fusionnée) et créez la branche du tutoriel 7 :
Si le tutoriel se passe mal, main reste intact : il suffit d'abandonner la branche. C'est ce qui rend les
essais sans risque.
Étape 2 — Décrire les trois états d'un chargement
Créez le fichier src/types/chargement.ts. Il définit un type générique EtatChargement<T> :
T est le type des données reçues en cas de succès (par exemple Theme[]). Les trois cas sont
distingués par la propriété statut.
Avec ce type, un chargement ne peut pas être « en erreur » et « réussi » en même temps, et les
donnees n'existent qu'en cas de succès. TypeScript vous oblige à traiter chaque cas séparément.
Étape 3 — Créer le composant Chargement
Créez src/components/Chargement.tsx : une roue qui tourne (ActivityIndicator) suivie d'un message.
Ce composant sera réutilisé par tous les écrans qui attendent des données.
Sans retour visuel, l'utilisateur croit que l'application est plantée et clique à nouveau. Un indicateur accompagné d'un message (« Chargement des thèmes... ») le rassure.
Étape 4 — Vérifier les données reçues
Les données qui arrivent du réseau ne sont jamais garanties. Dans la base, rien n'empêche un
administrateur d'ajouter un thème dont le code n'existe pas dans l'application. Créez
src/logic/conversions.ts : la fonction convertirTheme transforme une ligne reçue en Theme, ou
renvoie null si la ligne n'est pas utilisable.
Rappel : dans src/logic/, les imports de valeurs se font en chemin relatif avec l'extension
(../constants/generateur.ts), pour que les tests node --test fonctionnent.
Ajoutez aussi le fichier de test tests/conversions.test.ts, puis lancez npm test : les deux tests doivent passer.
/// <reference types="node" />
import assert from "node:assert/strict";
import { test } from "node:test";
import { convertirTheme } from "../src/logic/conversions.ts";
test("convertirTheme accepte un thème connu", () => {
assert.deepEqual(convertirTheme({ code: "reseaux", libelle: "Réseaux et Wi-Fi" }), {
code: "reseaux",
libelle: "Réseaux et Wi-Fi",
});
});
test("convertirTheme écarte un code inconnu", () => {
assert.equal(convertirTheme({ code: "inconnu", libelle: "?" }), null);
});
Une donnée venue d'un réseau, d'un formulaire ou d'une URL peut être incorrecte. La valider à l'entrée de l'application évite qu'un code inconnu ne plante un écran bien plus loin.
Étape 5 — Écrire le service chargerThemes
Une couche service regroupe les fonctions qui dialoguent avec Supabase : les écrans n'appellent
jamais supabase directement. Créez src/services/themesService.ts. Le détail technique de l'erreur
va dans la console (pour le développeur) ; un message simple est lancé vers l'écran (pour
l'utilisateur).
Afficher « new row violates row-level security policy » à un joueur n'a aucun sens, et peut révéler des détails sur votre base. Gardez le détail dans la console et montrez un message compréhensible.
Étape 6 — Charger les thèmes dans l'écran générateur
Dans src/app/generateur.tsx, les thèmes sont maintenant un état à trois cas, rempli par un
useEffect. Le compteur tentative permet de relancer le chargement : changer sa valeur déclenche
de nouveau l'effet.
Un catch oublié, c'est une roue qui tourne pour toujours quand le réseau tombe. Un chargement
doit toujours se terminer sur un des deux états finaux : succes ou erreur.
Étape 7 — Afficher les trois états
Dans le JSX du générateur, remplacez la liste de thèmes par un affichage selon le statut. Le bouton
Réessayer appelle la fonction reessayer.
Le test de connexion de l'accueil (étape 5 du tutoriel précédent) n'a plus d'utilité : retirez de
src/app/index.tsx l'import de supabase, le useEffect, l'état messageConnexion, le <Text> qui
l'affiche et le style info. L'accueil retrouve son code d'avant.
Testez ensuite, dans le navigateur :
- ouvrez le Générateur : la roue d'attente apparaît brièvement, puis les trois thèmes ;
- ouvrez les outils du navigateur (
F12), onglet Réseau, choisissez Hors connexion, puis rechargez la page du générateur : le message « Impossible de charger les thèmes. » s'affiche avec le bouton Réessayer ; - repassez En ligne et cliquez sur Réessayer : les thèmes apparaissent.
Un écran qui fonctionne quand tout va bien n'est qu'à moitié terminé. Couper le réseau est le test le plus simple (et le plus révélateur) du comportement d'une application.
Étape 8 — Enregistrer votre travail
-u mémorise le lien entre votre branche locale et GitHub : les prochains envois de ce tutoriel se feront
avec un simple git push.
📌 Une solution
Notions théoriques
Le code asynchrone : async, await, try/catch
Une requête vers Supabase prend du temps (de quelques dizaines de millisecondes à plusieurs secondes, ou jamais si le réseau est coupé). Le code ne peut pas « attendre » en bloquant l'écran : il est asynchrone.
- une fonction
asyncpeut utiliser le mot-cléawait, qui suspend la fonction jusqu'à l'arrivée de la réponse, sans bloquer le reste de l'application ; - une fonction
asyncrenvoie une promesse (Promise) : une valeur qui sera disponible plus tard ; try { ... } catch (erreur) { ... }exécute le code dutryet, si une erreur est lancée (throw), passe aucatch.
async function charger() {
try {
const themes = await chargerThemes(); // on attend la réponse
// ... utiliser themes
} catch (erreur) {
// ... la requête a échoué
}
}
Les trois états d'un chargement
Pendant une requête, l'écran est toujours dans un et un seul de ces états :
| Statut | Signification | Ce que voit l'utilisateur |
|---|---|---|
chargement | la réponse n'est pas encore arrivée | une roue et un message |
erreur | la requête a échoué | un message clair et un bouton Réessayer |
succes | les données sont arrivées | les thèmes |
Un type union discriminée (EtatChargement<T>) exprime cela en TypeScript : la propriété statut
est la « discriminante ». Quand on teste etat.statut === "succes", TypeScript sait que etat.donnees
existe ; dans les autres cas, il refuse d'y accéder.
Les deux bugs les plus fréquents du code asynchrone : ne rien afficher pendant l'attente (écran blanc) et oublier le cas d'erreur (la roue tourne pour toujours). Toute donnée venue du réseau doit prévoir ses trois états.
Le bouton Réessayer et la variable tentative
Un useEffect s'exécute de nouveau quand l'une de ses dépendances change. En ajoutant tentative aux
dépendances ([tentative]), on obtient un moyen simple de relancer le chargement : le bouton Réessayer
repasse l'état à chargement puis incrémente tentative.
Service, écran, logique : chacun son rôle
Le projet est organisé en couches :
| Couche | Dossier | Rôle |
|---|---|---|
| écran | src/app/ | affiche, ne connaît pas Supabase |
| service | src/services/ | seul endroit qui appelle Supabase et fabrique les messages d'erreur utilisateur |
| logique | src/logic/ | fonctions pures, testées par npm test |
Cette séparation a un intérêt concret : si l'on change de base de données un jour, seuls les services changent, et la logique de conversion reste testable sans réseau.
La conversion vérifiée
TypeScript ne vérifie les types qu'à la compilation. À l'exécution, rien ne garantit que les lignes
reçues de Supabase ont la forme annoncée. La fonction convertirTheme accepte une ligne { code: string; libelle: string } et vérifie que le code fait bien partie des codes connus de l'application :
estCodeTheme(texte: string): texte is CodeTheme est un type guard (« garde de type ») : après un
test réussi, TypeScript considère que texte est un CodeTheme.
Les lignes invalides sont écartées (null) plutôt que de faire planter l'application.
Le type CodeTheme ne connaît que trois codes. Ajouter un thème dans la base obligerait aussi à modifier
l'application : c'est un choix de simplicité assumé pour cette série.
Le drapeau estAnnule : une première protection
Le useEffect utilise encore un drapeau estAnnule, déjà vu à la séance précédente : si l'écran
disparaît pendant la requête, on évite de mettre à jour l'état. Mais la requête continue de s'exécuter
pour rien. Dans la prochaine séance, vous la ferez réellement annuler.
Test de mémorisation/compréhension
Exercices complémentaires
Exercice A : un message plus précis
Dans themesService.ts, remplacez temporairement le nom de la table theme par themes (qui n'existe pas).
Observez le message affiché dans l'application et le détail technique dans la console du navigateur.
Remettez ensuite le bon nom.
Exercice B : un test supplémentaire
Ajoutez à tests/conversions.test.ts un test qui vérifie que convertirTheme accepte le thème
{ code: "hameconnage", libelle: "Hameçonnage" }. Lancez npm test.
Ce qu'il faut retenir
| Notion | Résumé |
|---|---|
async / await | Écrire du code qui attend une réponse sans bloquer l'application. |
try / catch | Traiter l'échec d'une requête : sans catch, la roue tourne pour toujours. |
EtatChargement<T> | Union de trois états explicites : chargement, erreur, succes. |
tentative | Compteur ajouté aux dépendances d'un useEffect pour relancer un chargement. |
| Service | Seul endroit qui appelle Supabase ; renvoie des messages d'erreur simples. |
| Conversion vérifiée | Les données du réseau ne sont jamais garanties : on les valide et on écarte les lignes invalides. |
| Tester la panne | Couper le réseau montre si l'application gère vraiment l'erreur. |
Aperçu de la prochaine séance
Votre générateur gère l'attente et l'échec, mais un problème discret subsiste : si le joueur change très vite de thème, plusieurs requêtes partent en même temps et leurs réponses peuvent arriver dans le désordre. Dans la prochaine séance, vous provoquerez ce bug, puis vous le corrigerez avec l'AbortController, et vous afficherez le nombre de questions disponibles pour chaque réglage.