Aller au contenu principal

Question suivante

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

27_afficher_explication.png

info

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 tard
  • const : 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 :

TypeExemple
string"Bonjour"
number42, 3.14
booleantrue, 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 actuelle
  • setScore : 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 :

  1. L’utilisateur clique sur une réponse
  2. On affiche une explication (bonne ou mauvaise réponse)
  3. On attend 3 secondes
  4. 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);
astuce

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 :

  1. Afficher une question et ses réponses
  2. 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);
}
astuce

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);
  • explication contient le texte explicatif à afficher
  • afficherExplication permet de savoir si on doit afficher l’explication
  • questionIndex indique 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’explication
  • variant="outline" donne un style léger
  • className="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.

astuce

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 / ComposantRô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 setTimeoutChanger l’état après un délai
&& dans JSXAfficher 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 boutonsBloquer les clics pendant l’affichage de l’explication

Test de mémorisation/compréhension


Quelle fonction permet de mémoriser une valeur dans un composant React ?


Quelle fonction permet d’attendre un certain temps avant d’exécuter une action ?


Combien de millisecondes représentent 3 secondes ?


Quelle syntaxe permet d’incrémenter un index dans un `useState` ?


Quelle syntaxe JSX permet d’afficher un élément seulement si une condition est vraie ?


Que fait `setAfficherExplication(true)` dans React ?


Pourquoi utilise-t-on `setTimeout` dans l'exemple de la séance ?


Que se passe-t-il si on ne vérifie pas `if (!question)` avant d'afficher une question ?


Quelle est la structure correcte d’un objet `question` dans cet exemple ?


Quel est le rôle de `setExplication(question.explication)` ?


Que fait `setAfficherExplication(false)` après 3 secondes ?


Dans l’exemple, que se passe-t-il si l’utilisateur clique sur une réponse ?


Pourquoi utilise-t-on `useState` pour `questionIndex` ?


Quel est le rôle de `setQuestionIndex((prev) => prev + 1)` ?


À quoi sert `use client` en haut du fichier ?



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

Objectif

Dans ce TP, vous allez :

  • Ajouter une colonne explication dans 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 explication dans Supabase

  1. Allez sur https://supabase.com et connectez-vous à votre projet.

  2. Dans le menu de gauche, cliquez sur Table Editor puis sur la table question.

    10_supabase.png

  3. A droite de la table question, cliquez sur les 3 points ...

    11_supabase.png

  4. Cliquez sur Edit Table

  5. Cliquez sur le bouton Add column

    12_supabase.png

  6. Saisissez les informations de la nouvelle colonne :

    • Nom : explication
    • Type : text
    • Nullable : (oui, autoriser les valeurs nulles)

    13_supabase.png

  7. 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

  1. Déplacer vous à droite de la table question.

14_supabase.png

  1. Pour chaque question, ajoutez une explication dans la nouvelle colonne explication.

Exemples :

texte de la questionexplication
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.

15_supabase.png

Ces explications seront affichées après chaque réponse.


Étape 3 — Modifier la requête Supabase

  1. Ouvrez le fichier app/page.tsx.
  2. Dans la fonction fetchQuestions, ajoutez la colonne explication à 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.

16_fetchQuestions.png


É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.

17_useState.png


É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

18_handleClick.png

Décryptage du code

18b_decryptage_handleClick.png

18c_decryptage_handleClick.png

18d_decryptage_handleClick.png


Étape 6 — Tester le fonctionnement

Lancez votre application :

npm run dev

npm_run_dev.png

18e_test_fonctionnement.png


É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 Alert rend l’explication plus lisible.

19_afficher_explication.png


É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>
))}
info

Ainsi, le bouton sera grisé et non cliquable pendant l’affichage de l’explication.

20_button_disabled.png


É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.

21_fin_quiz.png


É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.

astuce

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.

22_useState.png


1.2 — Dans la fonction fetchQuestion(), remplacez :

setQuestion(data?.[0]);

par :

setQuestions(data || []);
astuce

Nous utilisons setQuestions pour stocker toutes les questions récupérées depuis Supabase.

data || [] garantit que si data est null ou undefined, on stocke un tableau vide à la place.

23_setQuestons.png


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.

24_const_question.png


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, question devient undefined, et ce message s’affichera.

25_return_fin.png


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>
)}

26_questions_length.png


Étape 11 — Tester le fonctionnement

  1. Cliquez sur une réponse
  2. Une explication s’affiche
  3. Après 3 secondes, la question suivante apparaît
  4. Une fois toutes les questions affichées, un message de fin s’affiche
  5. 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

test_fonctionnement_1.png


É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 fonction handleClick, 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

É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