Build our Quiz App Component by Component

Build the App Component by Component

Step 1: App.jsx — The Main Component

This component manages the flow: welcome screen → category select → quiz → results.

Paste this in src/App.jsx:

// src/App.jsx
import React, { useState } from "react";
import Quiz from "./components/Quiz";
import "./style.css";


const App = () => {
  const [start, setStart] = useState(false);
  const [category, setCategory] = useState(9);
  const [selected, setSelected] = useState(false);


  const categories = [
    { id: 9, name: "General Knowledge" },
    { id: 21, name: "Sports" },
    { id: 23, name: "History" },
    { id: 17, name: "Science & Nature" },
    { id: 22, name: "Geography" },
    { id: 18, name: "Computers" },
    { id: 24, name: "Politics" },
    { id: 25, name: "Art" }
  ];


  const handleStart = () => {
    setStart(true);
  };


  return (
    <div className="app">
      {!start ? (
        <div className="start-screen">
          <h1>🎯 Welcome To My Quiz</h1>
          {!selected ? (
            <>
              <p>Select the domain you’d like to test your knowledge in 🔍</p>
              <select
                onChange={(e) => setCategory(e.target.value)}
                className="dropdown"
              >
                {categories.map((cat) => (
                  <option key={cat.id} value={cat.id}>
                    {cat.name}
                  </option>
                ))}
              </select>
              <button onClick={() => setSelected(true)} className="btn">
                Confirm Category
              </button>
            </>
          ) : (
            <>
              <p>Great! Now I’ll test you and I hope you win! 💪🎉</p>
              <button onClick={handleStart} className="btn">
                Start Quiz
              </button>
            </>
          )}
        </div>
      ) : (
        <Quiz category={category} />
      )}


      {/* Footer */}
      <footer className="footer">
        Made by Jaishree Tomar for{" "}
        <a href="https://www.guvi.in" target="_blank" rel="noopener noreferrer">
          GUVI
        </a>
      </footer>
    </div>
  );
};


export default App;

Step 2: Quiz.jsx — Main Quiz Logic

Responsible for fetching questions, showing them, and tracking score.

Paste this in src/components/Quiz.jsx:

import React, { useEffect, useState } from "react";
import QuestionCard from "./QuestionCard";


const Quiz = ({ category }) => {
  const [questions, setQuestions] = useState([]);
  const [currentQn, setCurrentQn] = useState(0);
  const [score, setScore] = useState(0);
  const [answers, setAnswers] = useState([]);
  const [loading, setLoading] = useState(true);


  const shuffle = (arr) => [...arr].sort(() => Math.random() - 0.5);


  const decodeHtml = (html) => {
    const txt = document.createElement("textarea");
    txt.innerHTML = html;
    return txt.value;
  };


  useEffect(() => {
    const fetchQuestions = async () => {
      const url = `https://opentdb.com/api.php?amount=10&category=${category}&type=multiple`;
      const res = await fetch(url);
      const data = await res.json();
      const formatted = data.results.map((q) => ({
        ...q,
        question: decodeHtml(q.question),
        correct_answer: decodeHtml(q.correct_answer),
        options: shuffle([
          ...q.incorrect_answers.map(decodeHtml),
          decodeHtml(q.correct_answer),
        ]),
      }));
      setQuestions(formatted);
      setLoading(false);
    };


    fetchQuestions();
  }, [category]);


  const handleAnswer = (answer) => {
    const correct = questions[currentQn].correct_answer;
    if (answer === correct) setScore(score + 1);


    setAnswers([
      ...answers,
      {
        question: questions[currentQn].question,
        selected: answer,
        correct,
        isCorrect: answer === correct,
      },
    ]);


    setTimeout(() => {
      if (currentQn + 1 < questions.length) {
        setCurrentQn(currentQn + 1);
      }
    }, 300);
  };


  if (loading) return <h2 className="loading">⌛ Loading questions...</h2>;


  if (answers.length === questions.length) {
    return (
      <div className="score-card">
        <h2>🎉 Your Final Score: {score} / {questions.length}</h2>
        <table className="results-table">
          <thead>
            <tr>
              <th>#</th>
              <th>Question</th>
              <th>Your Answer</th>
              <th>Correct Answer</th>
              <th>Status</th>
            </tr>
          </thead>
          <tbody>
            {answers.map((ans, idx) => (
              <tr key={idx} className={ans.isCorrect ? "correct-row" : "wrong-row"}>
                <td>{idx + 1}</td>
                <td>{ans.question}</td>
                <td>{ans.selected}</td>
                <td>{ans.correct}</td>
                <td>{ans.isCorrect ? "✅" : "❌"}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }


  return (
    <QuestionCard
      question={questions[currentQn].question}
      options={questions[currentQn].options}
      currentIndex={currentQn}
      total={questions.length}
      onAnswer={handleAnswer}
    />
  );
};


export default Quiz;

Step 3: QuestionCard.jsx — Display Each Question

Paste this in src/components/QuestionCard.jsx:

import React from "react";


const alphabets = ["A", "B", "C", "D"];


const QuestionCard = ({ question, options, currentIndex, total, onAnswer }) => {
  return (
    <div className="question-card">
      <h2>❓ Question {currentIndex + 1} of {total}</h2>
      <p className="question-text">{question}</p>
      <div className="options">
        {options.map((opt, idx) => (
          <button
            key={idx}
            className="option-btn"
            onClick={() => onAnswer(opt)}
          >
            <strong>{alphabets[idx]}.</strong> {opt}
          </button>
        ))}
      </div>
    </div>
  );
};


export default QuestionCard;

Lesson 4: Style the App

Paste this in src/style.css:

body {
  margin: 0;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background-color: #121212;
  color: #ffffff;
}


.app {
  text-align: center;
  padding: 20px;
}


.start-screen {
  margin-top: 60px;
}


h1 {
  font-size: 2.5rem;
  margin-bottom: 20px;
  color: #00ffff;
}


p {
  font-size: 1.2rem;
  margin: 10px 0 20px;
}


.dropdown {
  padding: 10px;
  font-size: 1rem;
  border-radius: 6px;
  border: none;
  margin-bottom: 20px;
}


.btn {
  padding: 10px 25px;
  font-size: 1rem;
  border: none;
  border-radius: 8px;
  background-color: #00bfff;
  color: white;
  cursor: pointer;
  transition: background-color 0.3s ease;
  margin-top: 10px;
}


.btn:hover {
  background-color: #009acd;
}


.loading {
  font-size: 1.5rem;
  color: #ffcc00;
  margin-top: 50px;
}


.question-card {
  margin-top: 50px;
  background-color: #1e1e1e;
  padding: 30px;
  border-radius: 12px;
  max-width: 800px;
  margin-left: auto;
  margin-right: auto;
  box-shadow: 0 0 15px rgba(0, 191, 255, 0.2);
}


.question-text {
  font-size: 1.25rem;
  margin-bottom: 30px;
}


.options {
  display: flex;
  flex-direction: column;
  gap: 15px;
}


.option-btn {
  padding: 12px 20px;
  border: none;
  border-radius: 8px;
  background-color: #2e2e2e;
  color: white;
  font-size: 1rem;
  text-align: left;
  transition: background-color 0.3s ease, transform 0.2s;
  cursor: pointer;
}


.option-btn:hover {
  background-color: #00bfff;
  transform: translateY(-2px);
}


.option-btn.correct {
  background-color: #28a745 !important;
  color: white;
}


.option-btn.incorrect {
  background-color: #dc3545 !important;
  color: white;
}


.score-card {
  margin-top: 40px;
  padding: 20px;
  max-width: 800px;
  margin-left: auto;
  margin-right: auto;
  background-color: #1e1e1e;
  border-radius: 12px;
  box-shadow: 0 0 15px rgba(255, 255, 255, 0.05);
}


.score-card h2 {
  color: #00ffff;
  margin-bottom: 20px;
}


.answer-list {
  margin-top: 20px;
}


.answer-block {
  margin-bottom: 20px;
  background-color: #2c2c2c;
  padding: 15px;
  border-radius: 8px;
  text-align: left;
}


.answer-block.correct {
  border-left: 6px solid #28a745;
}


.answer-block.wrong {
  border-left: 6px solid #dc3545;
}


.answer-block h4 {
  margin: 0 0 10px;
  color: #ffffff;
}


.answer-block p {
  margin: 4px 0;
  color: #cccccc;
}


/* Footer Section */
.footer {
  margin-top: 40px;
  text-align: center;
  color: #cccccc;
  font-size: 0.9rem;
  padding-bottom: 20px;
}


.footer a {
  color: #00bfff;
  text-decoration: none;
}


.footer a:hover {
  text-decoration: underline;
}