Question suivante
Afficher une explication après la réponse, puis passer automatiquement à la question suivante.

Nous allons revoir les bases de JavaScript utiles pour le projet et améliorer l’interaction utilisateur avec une nouvelle fonctionnalité.
Notions théoriques
Dans cette séance, vous allez apprendre à :
- Manipuler des variables, des types et des opérateurs
- Comprendre le fonctionnement des états React (
useState) - Afficher une explication après le clic sur une réponse
- Attendre quelques secondes avant de passer automatiquement à la question suivante
Cela vous permettra d’ajouter une nouvelle fonctionnalité à votre quiz : afficher une explication pédagogique après chaque réponse, pour mieux comprendre ses erreurs ou ses réussites.
1. Les variables en JavaScript
Une variable est un espace mémoire dans lequel on stocke une valeur.
Il existe plusieurs façons de déclarer une variable en JavaScript :
let score = 0;
const nom = "Alice";
let: permet de modifier la valeur plus tardconst: la valeur ne changera pas (constante)var: ancienne syntaxe (à éviter aujourd’hui)
2. Les types de données
JavaScript est un langage faiblement typé, ce qui signifie que vous pouvez changer le type d’une variable facilement. Mais cela peut causer des erreurs difficiles à détecter.
Voici les types de base que vous devez connaître :
| Type | Exemple |
|---|---|
| string | "Bonjour" |
| number | 42, 3.14 |
| boolean | true, false |
En TypeScript, vous pouvez forcer le type d’une variable pour éviter les erreurs :
let score: number = 10;
let nom: string = "Alice";
let estConnecte: boolean = true;
3. Les opérateurs
Les opérateurs permettent de faire des calculs ou des comparaisons.
Opérateurs arithmétiques :
let total = 5 + 3; // 8
let reste = 10 % 3; // 1
Opérateurs de comparaison :
a === b // vrai si a est égal à b
a !== b // vrai si a est différent de b
a > b // supérieur
a < b // inférieur
Opérateurs logiques :
true && false // false (ET logique)
true || false // true (OU logique)
!true // false (NON logique)
4. Les fonctions
Une fonction est un bloc de code réutilisable.
function direBonjour() {
console.log("Bonjour !");
}
En React, on utilise souvent la fonction fléchée (arrow function) :
const direBonjour = () => {
console.log("Bonjour !");
};
5. Le hook useState
React permet de créer des interfaces interactives grâce à des états.
const [score, setScore] = useState(0);
score: la valeur actuellesetScore: la fonction pour modifier cette valeur
Chaque fois que vous utilisez setScore, React re-render (réaffiche) le composant.
6. Le délai avec setTimeout
Pour faire une pause avant de passer à la question suivante, on utilise la fonction JavaScript setTimeout() :
setTimeout(() => {
console.log("3 secondes plus tard...");
}, 3000); // 3000 millisecondes = 3 secondes
Dans notre cas, cela permettra d’attendre 3 secondes après l’affichage de l’explication, avant de passer automatiquement à la prochaine question.
7. Exemple d’enchaînement logique
Voici ce que nous allons réaliser :
- L’utilisateur clique sur une réponse
- On affiche une explication (bonne ou mauvaise réponse)
- On attend 3 secondes
- On passe à la question suivante
8. Pourquoi ajouter une explication ?
L’objectif de Quiz Cyber est de former les élèves (professeurs et personnel du lycée) à la cybersécurité. Il est donc essentiel d’expliquer pourquoi une réponse était bonne ou mauvaise.
Exemples d’explication :
- "Attention : un lien peut cacher une URL malveillante."
- "Bravo : cette pièce jointe contient une extension suspecte."
Ces explications seront stockées dans la base Supabase, dans une nouvelle colonne appelée explication.
9. Comment afficher cette explication ?
Vous allez ajouter un nouvel état React :
const [explication, setExplication] = useState("");
Puis, dans la fonction qui gère le clic :
if (reponse.est_correcte) {
setExplication("Bonne réponse ! Bravo !");
} else {
setExplication("Mauvaise réponse. L’explication est : ...");
}
setTimeout(() => {
setExplication("");
setQuestionIndex((prev) => prev + 1);
}, 3000);
Il y a 2 façons d’utiliser setQuestionIndex
1. En passant une valeur directe :
setQuestionIndex(questionIndex + 1);
Cela fonctionne, mais si plusieurs mises à jour sont déclenchées rapidement, votre code pourrait utiliser une ancienne valeur de
questionIndex.
2. En passant une fonction de mise à jour :
setQuestionIndex((prev) => prev + 1);
C’est la méthode recommandée quand la nouvelle valeur dépend de l’ancienne.
Que représente prev ?
prev est un nom de variable que vous choisissez librement. Il représente la valeur actuelle de l’état au moment où votre code applique la mise à jour.
Donc :
(prev) => prev + 1
signifie :
"Prends la valeur actuelle (
prev) et ajoute 1".
Exemple pratique
Afficher une explication après une réponse
Dans cet exemple, nous allons :
- Afficher une question et ses réponses
- Lorsqu’un joueur clique sur une réponse :
- Afficher une explication
- Attendre 3 secondes
- Passer à la question suivante
1 — Structure de données
Imaginons que vous ayez déjà récupéré une question depuis Supabase, avec ce format :
const question = {
texte: "Quel est le risque principal d’un mot de passe faible ?",
explication: "Un mot de passe faible peut être facilement deviné par un attaquant.",
reponses: [
{ id: 1, texte: "Il est trop long", est_correcte: false },
{ id: 2, texte: "Il est facile à deviner", est_correcte: true },
{ id: 3, texte: "Il est difficile à retenir", est_correcte: false },
],
};
2 — États React
Nous avons besoin de plusieurs états :
const [questionIndex, setQuestionIndex] = useState(0);
const [explication, setExplication] = useState("");
const [afficherExplication, setAfficherExplication] = useState(false);
3 — Fonction de clic
Voici la logique à exécuter quand l’utilisateur clique sur une réponse :
function handleClick(reponse: any) {
// 1. Afficher l’explication
setExplication(question.explication);
setAfficherExplication(true);
// 2. Attendre 3 secondes
setTimeout(() => {
// 3. Passer à la question suivante
setQuestionIndex((prev) => prev + 1);
setAfficherExplication(false);
setExplication("");
}, 3000);
}
Si vous avez besoin de comprendre ce que représente prev, vous pouvez consulter : Que représente prev ?
4 — Affichage de l’explication
Dans le JSX, vous affichez l’explication uniquement si afficherExplication est true :
{afficherExplication && (
<div className="mt-4 p-4 bg-yellow-100 border border-yellow-300 rounded">
<p className="text-sm text-gray-800">{explication}</p>
</div>
)}
5 — Exemple complet
Voici un exemple simplifié de composant :
"use client";
import { useState } from "react";
export default function ExempleExplication() {
const questions = [
{
texte: "Quel est le risque principal d’un mot de passe faible ?",
explication: "Un mot de passe faible peut être facilement deviné par un attaquant.",
reponses: [
{ id: 1, texte: "Il est trop long", est_correcte: false },
{ id: 2, texte: "Il est facile à deviner", est_correcte: true },
{ id: 3, texte: "Il est difficile à retenir", est_correcte: false },
],
},
{
texte: "Que signifie HTTPS ?",
explication: "HTTPS signifie que les données sont échangées de manière chiffrée.",
reponses: [
{ id: 1, texte: "HyperText Transfer Protocol Secure", est_correcte: true },
{ id: 2, texte: "Home Transfer Protocol System", est_correcte: false },
{ id: 3, texte: "Hacker Transfer Protocol", est_correcte: false },
],
},
];
const [questionIndex, setQuestionIndex] = useState(0);
const [explication, setExplication] = useState("");
const [afficherExplication, setAfficherExplication] = useState(false);
const question = questions[questionIndex];
function handleClick(reponse: any) {
setExplication(question.explication);
setAfficherExplication(true);
setTimeout(() => {
setAfficherExplication(false);
setExplication("");
setQuestionIndex((prev) => prev + 1);
}, 3000);
}
if (!question) {
return <p>Quiz terminé !</p>;
}
return (
<div className="p-6 max-w-xl mx-auto">
<h2 className="text-xl font-bold mb-4">{question.texte}</h2>
<div className="space-y-2">
{question.reponses.map((reponse) => (
<button
key={reponse.id}
onClick={() => handleClick(reponse)}
className="block w-full text-left border px-4 py-2 rounded hover:bg-gray-50"
>
{reponse.texte}
</button>
))}
</div>
{afficherExplication && (
<div className="mt-4 p-4 bg-yellow-100 border border-yellow-300 rounded">
<p className="text-sm text-gray-800">{explication}</p>
</div>
)}
</div>
);
}
Résultat attendu
- L’utilisateur clique sur une réponse
- Une explication s’affiche pendant 3 secondes
- La question suivante s’affiche automatiquement
Méthodes à connaître
Dans cette partie, vous allez découvrir les méthodes essentielles pour :
- Afficher une explication après une réponse
- Attendre un délai
- Passer automatiquement à la question suivante
- Utiliser les composants UI de Shadcn pour une interface claire
useState()
Mémoriser des valeurs dans un composant React
Dans React, on utilise useState pour stocker des informations qui changent au cours du temps (état). Exemple :
const [explication, setExplication] = useState("");
const [afficherExplication, setAfficherExplication] = useState(false);
const [questionIndex, setQuestionIndex] = useState(0);
explicationcontient le texte explicatif à afficherafficherExplicationpermet de savoir si on doit afficher l’explicationquestionIndexindique la position actuelle dans le tableau de questions
setTimeout()
Exécuter une action après un délai
Pour attendre un certain temps avant de faire une action (comme passer à la question suivante), on utilise setTimeout :
setTimeout(() => {
// Action à exécuter après 3 secondes
}, 3000);
Le second argument est le délai en millisecondes (3000 ms = 3 secondes).
setState dans un setTimeout
Dans notre cas, après avoir affiché l’explication, on veut attendre 3 secondes avant de passer à la question suivante :
setAfficherExplication(true);
setExplication(question.explication);
setTimeout(() => {
setAfficherExplication(false);
setExplication("");
setQuestionIndex((prev) => prev + 1);
}, 3000);
Cette technique permet de créer une transition automatique vers la prochaine question.
Affichage conditionnel avec &&
Pour afficher un élément seulement si une condition est vraie, on utilise :
{afficherExplication && (
<div>Texte à afficher</div>
)}
Cela évite de rendre un composant inutilement.
Les composants Alert
Pour afficher une explication, on peut utiliser Alert :
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
{afficherExplication && (
<Alert className="mt-4">
<AlertTitle>Explication</AlertTitle>
<AlertDescription>{explication}</AlertDescription>
</Alert>
)}
Ce composant est plus clair visuellement qu’un simple <div>.
Utiliser Button pour les réponses
Au lieu d’un <button> HTML classique, vous pouvez utiliser le composant Button de Shadcn pour une meilleure cohérence visuelle :
import { Button } from "@/components/ui/button";
{question.reponses.map((reponse) => (
<Button
key={reponse.id}
onClick={() => handleClick(reponse)}
disabled={afficherExplication}
variant="outline"
className="w-full justify-start"
>
{reponse.texte}
</Button>
))}
disabled={afficherExplication}empêche de cliquer pendant l’affichage de l’explicationvariant="outline"donne un style légerclassName="w-full justify-start"aligne le texte à gauche
Passer à la question suivante
Pour passer à la question suivante dans un tableau, on incrémente l’index :
setQuestionIndex((prev) => prev + 1);
Cela permet de charger la prochaine question dans le tableau.
Si vous avez besoin de comprendre ce que représente prev, vous pouvez consulter : Que représente prev ?
Afficher un message de fin
Quand il n’y a plus de questions, on peut afficher un message :
if (!question) {
return <p>Quiz terminé</p>;
}
Cela évite une erreur si l’index dépasse la taille du tableau.
Résumé des méthodes utilisées
| Méthode / Composant | Rôle dans l'application |
|---|---|
useState() | Stocker l’état de l’explication, de l’index, etc. |
setTimeout() | Attendre 3 secondes avant de passer à la suite |
setState dans setTimeout | Changer l’état après un délai |
&& dans JSX | Afficher un élément uniquement si une condition est vraie |
setIndex((prev) => prev + 1) | Passer à la question suivante |
Alert, AlertTitle, AlertDescription (Shadcn) | Afficher une explication claire et stylée |
Button (Shadcn) | Afficher les réponses avec un style cohérent |
disabled sur les boutons | Bloquer les clics pendant l’affichage de l’explication |
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Objectif
Dans ce TP, vous allez :
- Ajouter une colonne
explicationdans la base de données Supabase - Mettre à jour votre code pour afficher cette explication après chaque réponse
- Attendre 3 secondes, puis passer automatiquement à la question suivante
- Utiliser des composants (
Button,Alert, etc) pour une interface claire
Étape 1 — Ajouter la colonne explication
Ajouter la colonne
explicationdans Supabase
-
Allez sur https://supabase.com et connectez-vous à votre projet.
-
Dans le menu de gauche, cliquez sur Table Editor puis sur la table question.

-
A droite de la table
question, cliquez sur les 3 points...
-
Cliquez sur Edit Table
-
Cliquez sur le bouton Add column

-
Saisissez les informations de la nouvelle colonne :
- Nom :
explication - Type :
text - Nullable : (oui, autoriser les valeurs nulles)

- Nom :
-
Puis cliquez sur le bouton Save
Vous venez d’ajouter une colonne qui permettra de stocker une explication pour chaque question.
Étape 2 — Enregistrer des explications
- Déplacer vous à droite de la table
question.

- Pour chaque question, ajoutez une explication dans la nouvelle colonne
explication.
Exemples :
| texte de la question | explication |
|---|---|
| Que signifie "phishing" ? | Le phishing est une technique utilisée par des cybercriminels pour tromper les utilisateurs et les inciter à révéler des informations sensibles, comme des mots de passe ou des numéros de carte bancaire, souvent via de faux e-mails ou sites Web. |
| Comment créer un mot de passe sécurisé ? | Un mot de passe sécurisé doit être long (au moins 12 caractères), inclure des lettres majuscules et minuscules, des chiffres et des symboles, et ne pas contenir d’informations personnelles faciles à deviner. |

Ces explications seront affichées après chaque réponse.
Étape 3 — Modifier la requête Supabase
- Ouvrez le fichier
app/page.tsx. - Dans la fonction
fetchQuestions, ajoutez la colonneexplicationà la requête Supabase :
const { data, error } = await supabase
.from("question")
.select(`
id,
texte,
image_url,
image_credit_nom,
image_credit_url,
explication,
reponses:reponse (
id,
texte,
est_correcte
)
`)
Cela permet de récupérer l’explication avec chaque question.

Étape 4 — Créer les états nécessaires
Ajoutez ces 3 lignes en haut de votre composant principal :
const [question, setQuestion] = useState<any>(null);
const [questionIndex, setQuestionIndex] = useState(0);
const [explication, setExplication] = useState("");
const [afficherExplication, setAfficherExplication] = useState(false);
Ces états vous permettront de suivre la progression (grace à
questionIndex) et d’afficher l’explication.

Étape 5 — Modifier la fonction handleClick
function handleClick(reponse: any) {
if (!question || afficherExplication) return;
const estBonneReponse = reponse.est_correcte;
const message = estBonneReponse
? "Bonne réponse !"
: "Mauvaise réponse.";
const explicationTexte = question.explication || message;
setExplication(explicationTexte);
setAfficherExplication(true);
setTimeout(() => {
setAfficherExplication(false);
setExplication("");
setQuestionIndex((prev) => prev + 1);
}, 3000);
}
Cette fonction :
- Affiche l’explication
- Attend 3 secondes
- Passe à la question suivante

Décryptage du code



Étape 6 — Tester le fonctionnement
Lancez votre application :
npm run dev


Étape 7 — Afficher l’explication
Dans votre JSX, ajoutez ce bloc sous les réponses :
{afficherExplication && (
<Alert className="mt-6 bg-yellow-50 border-yellow-300 text-yellow-800">
<AlertTitle>Explication</AlertTitle>
<AlertDescription>{explication}</AlertDescription>
</Alert>
)}
Le composant
Alertrend l’explication plus lisible.

Étape 8 — Désactiver les boutons
Nous allons désactiver les boutons pendant l’affichage de l’explication, pour éviter les doubles clics.
Il suffit d’ajouter la propriété disabled au composant Button, en ajoutant la ligne :
disabled={afficherExplication}
{question.reponses.map((reponse) => (
<Button
key={reponse.id}
onClick={() => handleClick(reponse)}
disabled={afficherExplication}
className="w-full justify-start mt-2"
variant="outline"
>
{reponse.texte}
</Button>
))}
Ainsi, le bouton sera grisé et non cliquable pendant l’affichage de l’explication.

Étape 9 — Afficher un message de fin
Ajoutez cette condition avant le return principal :
if (!question) {
return (
<div className="text-center mt-10">
<h2 className="text-2xl font-bold">Quiz terminé !</h2>
<p className="mt-4 text-muted-foreground">Merci d’avoir participé.</p>
</div>
);
}
Cela évite une erreur si l’utilisateur a terminé toutes les questions.

Étape 10 — Passer à la question suivante
Il faut afficher toutes les questions du quiz, et pas seulement la première.
Pourquoi seule la première question s’affiche ?
La ligne suivante indique que nous ne stockons qu’une seule question :
const [question, setQuestion] = useState<any>(null);
Et plus bas, la ligne suivante, dans la fonction fetchQuestion(), indique que nous ne stockons que la première question (qui a le n°0) récupérée depuis Supabase :
setQuestion(data?.[0]); // On ne stocke que la question n°0
Cela signifie que même si la variable questionIndex est augmentée (on dit incrémentée),
elle n’est pas utilisée pour changer de question.
Pour afficher toutes les questions du quiz (et pas uniquement la première)
une solution consiste à stocker toutes les questions dans un tableau,
puis d’utiliser questionIndex pour afficher la question courante.
1.1 — Remplacez l’état question par questions (avec un "s") :
const [questions, setQuestions] = useState<any[]>([]);
any[]permet de stocker toutes les questions dans un tableau.

1.2 — Dans la fonction fetchQuestion(), remplacez :
setQuestion(data?.[0]);
par :
setQuestions(data || []);
Nous utilisons setQuestions pour stocker toutes les questions récupérées depuis Supabase.
data || []garantit que sidataestnullouundefined, on stocke un tableau vide à la place.

1.3 — Utiliser questionIndex pour afficher la question actuelle
Ajoutez cette ligne juste après vos useState :
const question = questions[questionIndex];
Cela permet d’afficher dynamiquement la question (présente dans le tableau) qui correspond à l’index courant.

1.4 Gérer la fin du quiz
Ajoutez ce bloc avant le return principal :
if (!question) {
return (
<div className="text-center mt-10">
<h2 className="text-2xl font-bold">Quiz terminé !</h2>
<p className="mt-4 text-muted-foreground">Merci d’avoir participé.</p>
</div>
);
}
Si l’utilisateur arrive à la fin du tableau,
questiondevientundefined, et ce message s’affichera.

1.5 — Corriger l’affichage conditionnel
Remplacez ce bloc :
{question ? (
<Card>...</Card>
) : (
<p>Chargement de la question...</p>
)}
par :
{questions.length > 0 ? (
<Card>...</Card>
) : (
<p>Chargement des questions...</p>
)}

Étape 11 — Tester le fonctionnement
- Cliquez sur une réponse
- Une explication s’affiche
- Après 3 secondes, la question suivante apparaît
- Une fois toutes les questions affichées, un message de fin s’affiche
- Vérifiez que :
- Les questions s’affichent
- Les réponses sont cliquables
- Une explication s’affiche après le clic
- La question suivante s’affiche automatiquement après 3 secondes
- Un message s’affiche à la fin du quiz

Étape 12 — À vous de jouer !
Comment augmenter la durée d’affichage de l’explication à 5 secondes au lieu de 3 secondes ?
Details
Indice
Dans la fonctionhandleClick, il y a un setTimeout qui gère le délai avant de passer à la question suivante.
Il suffit de modifier la valeur 3000 dans le setTimeout par 5000 :Une solution
Vous devez être connecté pour voir le contenu.
Étape 13 — Bonus
Si vous avez terminé, vous pouvez ajouter des fonctionnalités supplémentaires :
- Ajoutez un score pour mesurer les bonnes réponses
- Affichez une barre de progression
- Ajoutez un bouton "Recommencer le quiz"
- Ajoutez un effet de transition (ex : fondu) entre les questions
- Indiquer à l’utilisateur où il en est dans le quiz
Par exemple, pour indiquer à l ’utilisateur où il en est dans le quiz, il suffit d'ajouter ce code juste au-dessus de la question :
<p className="text-sm text-muted-foreground mb-2">
Question {questionIndex + 1} sur {questions.length}
</p>
Résultat attendu
- Une interface claire avec des boutons stylés
- Une explication pédagogique après chaque réponse
- Une navigation fluide entre les questions
- Une expérience utilisateur agréable
Une solution
Vous devez être connecté pour voir le contenu.