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

