We’ll explore the process of building a Quiz App utilizing NextJS and Tailwind. The quiz app provides users with a series of multiple-choice questions with options. Users can select the option and move to the next question. At the end, the user will be able to see the analysis of the quiz.
Prerequisites
Approach to Create Quiz App with Next JS and Tailwind:
- Set up the Project by Creating a new Next JS project.
- We will be using the useState hook to manage the quiz state, including the current question, selected answer, and quiz results.
- We will utilize Tailwind to style our Quiz App.
- Create a new QuestionSet.ts file under the Data folder where we will define the questions along with options and answers.
- We will be creating different components for code readability and reusability. Here we will be creating components such as Quiz to render the different questions and ScoreCard to display the result analysis.
- We will be creating interfaces for code organization and type safety.
- The quiz app will start by taking the user's name once we click on the start quiz button page component will render the Quiz component which is responsible for displaying the questions one by one.
- After submitting the test ScoreCard component will be rendered which will display the result analysis including score, correct answers, wrong answers, percentage and status.
Steps to create the Quiz Application:
Step 1: Create a application of NextJS using the following command.
npx create-next-app@latest quiz-appStep 2: Navigate to project directory
cd quiz-appProject Structure:
Updated Dependencies:
"dependencies": {
"react": "^18",
"react-dom": "^18",
"next": "14.2.15"
}
Example: This example demonstrates the basic implementation of the Quiz App.
/* global.css*/
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
// page.tsx
"use client";
import { useState, ChangeEvent } from "react";
import Quiz from "./Components/Quiz";
export default function Home() {
// Using `useState` hooks with appropriate types
const [quizStarted, setQuizStarted] = useState<boolean>(false);
const [name, setName] = useState<string>("");
// Define the function to handle input changes
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};
return (
<div className="container mx-auto mt-10">
<div className="text-center">
<h1 className="text-green-600 text-3xl
font-bold mb-4">
GeekForGeeks
</h1>
<h3 className="text-xl mb-6">
Quiz App
</h3>
</div>
{quizStarted ? (
// Passing `name` to Quiz component, which expects it as a string
<Quiz name={name} />
) : (
<>
<div className="mb-4">
<label
htmlFor="nameInput"
className="block text-lg font-medium text-gray-700">
Enter Your Name:
</label>
<input
type="text"
className="mt-1 block w-full px-4 py-2 border
border-gray-300 rounded-md shadow-sm
focus:outline-none focus:ring-indigo-500
focus:border-indigo-500 sm:text-sm"
id="nameInput"
value={name}
onChange={handleNameChange}
/>
</div>
<button
onClick={() => setQuizStarted(true)}
className="bg-blue-600 text-white px-4
py-2 rounded-md hover:bg-blue-700
transition-colors duration-200"
// Disable button if name is empty or contains only whitespace
disabled={!name.trim()}>
Start Quiz
</button>
</>
)}
</div>
);
}
//Components/Quiz.tsx
import React, { useState } from "react";
import { quiz } from "../Data/QuestionSet";
import ScoreCard from "./ScoreCard";
import { QuizProps, QuizResult, Question } from "../Interfaces/quiz-app";
const Quiz: React.FC<QuizProps> = ({ name }) => {
const [currentQuestionIndex, setCurrentQuestionIndex] = useState<number>(0);
const [selectedAnswer, setSelectedAnswer] = useState<string>("");
const [answerChecked, setAnswerChecked] = useState<boolean>(false);
const [selectedAnswerIndex, setSelectedAnswerIndex] = useState<number | null>(
null
);
const [showResults, setShowResults] = useState<boolean>(false);
const [quizResult, setQuizResult] = useState<QuizResult>({
score: 0,
correctAnswers: 0,
wrongAnswers: 0,
});
const { questions } = quiz;
const { question, answers, correctAnswer } = questions[currentQuestionIndex];
const onAnswerSelected = (answer: string, idx: number) => {
setSelectedAnswerIndex(idx);
setSelectedAnswer(answer);
setAnswerChecked(true);
};
const handleNextQuestion = () => {
if (selectedAnswer === correctAnswer) {
setQuizResult((prev) => ({
...prev,
score: prev.score + 5,
correctAnswers: prev.correctAnswers + 1,
}));
} else {
setQuizResult((prev) => ({
...prev,
wrongAnswers: prev.wrongAnswers + 1,
}));
}
if (currentQuestionIndex !== questions.length - 1) {
setCurrentQuestionIndex((prev) => prev + 1);
} else {
setShowResults(true);
}
setSelectedAnswer("");
setSelectedAnswerIndex(null);
setAnswerChecked(false);
};
return (
<div className="container mx-auto mt-10">
<div>
{!showResults ? (
<div className="bg-white shadow-md rounded-lg p-6">
<h4 className="text-lg font-semibold mb-4">{question}</h4>
<ul className="list-none">
{answers.map((answer, idx) => (
<li
key={idx}
onClick={() => onAnswerSelected(answer, idx)}
className={`p-3 mb-2 border rounded-md
cursor-pointer hover:bg-gray-100 ${
selectedAnswerIndex === idx ? "bg-blue-500 text-white" : ""
}`}
>
{answer}
</li>
))}
</ul>
<div className="flex justify-between mt-6">
<b>
Question {currentQuestionIndex + 1}/{questions.length}
</b>
<button
onClick={handleNextQuestion}
className={`bg-blue-600 text-white
py-2 px-4 rounded-md ${
!answerChecked
? "opacity-50 cursor-not-allowed"
: "hover:bg-blue-700"
}`}
disabled={!answerChecked}
>
{currentQuestionIndex === questions.length - 1
? "Submit"
: "Next Question"}
</button>
</div>
</div>
) : (
<ScoreCard
quizResult={quizResult}
questions={questions}
name={name}
/>
)}
</div>
</div>
);
};
export default Quiz;
//Components/ScoreCard.tsx
import React from "react";
import { ScoreCardProps } from "../Interfaces/quiz-app";
const ScoreCard: React.FC<ScoreCardProps> = ({
quizResult,
questions,
name,
}) => {
const passPercentage = 60;
const percentage = (quizResult.score / (questions.length * 5)) * 100;
const status = percentage >= passPercentage ? "Pass" : "Fail";
return (
<>
<div className="bg-white shadow-md rounded-lg p-6">
<h3 className="text-xl font-semibold mb-4">
Hello, {name}. Here is your Result Analysis
</h3>
<table className="min-w-full table-auto">
<tbody>
<tr>
<td className="border px-4 py-2">Total Questions:</td>
<td className="border px-4 py-2">{questions.length}</td>
</tr>
<tr>
<td className="border px-4 py-2">Total Score:</td>
<td className="border px-4 py-2">{quizResult.score}</td>
</tr>
<tr>
<td className="border px-4 py-2">Correct Answers:</td>
<td className="border px-4 py-2">{quizResult.correctAnswers}</td>
</tr>
<tr>
<td className="border px-4 py-2">Wrong Answers:</td>
<td className="border px-4 py-2">{quizResult.wrongAnswers}</td>
</tr>
<tr>
<td className="border px-4 py-2">Percentage:</td>
<td className="border px-4 py-2">{percentage}%</td>
</tr>
<tr>
<td className="border px-4 py-2">Status:</td>
<td className="border px-4 py-2">{status}</td>
</tr>
</tbody>
</table>
<button
onClick={() => window.location.reload()}
className="bg-blue-600 text-white py-2 px-4
rounded-md mt-4 hover:bg-blue-700">
Restart
</button>
</div>
</>
);
};
export default ScoreCard;
//Data/QuestionSet.ts
import { Quiz } from '../Interfaces/quiz-app';
export const quiz: Quiz = {
questions: [
{
id: 1,
question: 'What does API stand for?',
answers: [
'Application Programming Interface',
'Advanced Programming Interface',
'Application Program Interface',
'Automated Programming Interface',
],
correctAnswer: 'Application Programming Interface',
},
{
id: 3,
question: `Which programming language is often
used for building web servers?`,
answers: ['Java', 'Python', 'JavaScript', 'C#'],
correctAnswer: 'JavaScript',
},
{
id: 4,
question: 'What is the purpose of SQL?',
answers: [
'Styling web pages',
'Querying databases',
'Creating animations',
'Developing mobile apps',
],
correctAnswer: 'Querying databases',
},
{
id: 5,
question: 'What does MVC stand for in web development?',
answers: [
'Model View Controller',
'Model Visual Controller',
'Model View Component',
'Model Visual Component',
],
correctAnswer: 'Model View Controller',
},
],
};
// Interfaces/quiz-app.ts
export interface Question {
id: number;
question: string;
answers: string[];
correctAnswer: string;
}
export interface Quiz {
questions: Question[];
}
export interface QuizResult {
score: number;
correctAnswers: number;
wrongAnswers: number;
}
export interface ScoreCardProps {
quizResult: QuizResult;
questions: Question[];
name: string;
}
export interface QuizProps {
name: string;
}
Start your application using the following command:
npm run devOutput: Naviage to the URL http://localhost:3000.