quiz app using reactjs

6 Steps to Create a Quiz App in ReactJS

You might make a ReactJS quiz app if you're starting in web development or taking a basic-level course. One of any language's most fundamental projects is this one. You can learn so many concepts while building quiz app in Reactjs.

  1. How useState works in ReactJS while keep track of active questions, selected answer and result.
  2. How to conditionally add classes to highlight the answer user has selected.
  3. Catching edge cases, like in case of last question we will change button title from Next to Finish and display the result to user. Add leading zero if question number is less than 10.

Understand The Logic Behind The Quiz App

To understand how quiz app works, we need to understand how following logic works.

  1. We have to track question number.
  2. Function runs on clicking next button.
  3. We have to keep track of all the answers selected by user, to calculate the score to show on result screen.
  4. Quiz question file, the same structure can be used in APIs.

Quiz Questions Javascript File

This is how we structured the quiz questions, the same format can be used for APIs.

// Question Types
// 1. MCQs | Multiple Choice | single
export const quiz = {
topic: 'Javascript',
level: 'Beginner',
totalQuestions: 4,
perQuestionScore: 5,
questions: [
{
question: 'Which function is used to serialize an object into a JSON string in Javascript?',
choices: ['stringify()', 'parse()', 'convert()', 'None of the above'],
type: 'MCQs',
correctAnswer: 'stringify()',
},
{
question: 'Which of the following keywords is used to define a variable in Javascript?',
choices: ['var', 'let', 'var and let', 'None of the above'],
type: 'MCQs',
correctAnswer: 'var and let',
},
{
question:
'Which of the following methods can be used to display data in some form using Javascript?',
choices: ['document.write()', 'console.log()', 'window.alert', 'All of the above'],
type: 'MCQs',
correctAnswer: 'All of the above',
},
{
question: 'How can a datatype be declared to be a constant type?',
choices: ['const', 'var', 'let', 'constant'],
type: 'MCQs',
correctAnswer: 'const',
},
],
}

The above questions have been taken from interviewbit.

Quiz Question Structure Explanation

In my observation, all quiz APIs have almost the same structure. question, choices, correct answers, and question type. These are the main keys.

This is the design we are going to implement, Codepen demo is also available at the end of the article.

quiz app design

Step-by-Step Quiz App Development

So, from here, we will see code blocks for each step. Let us get started.

Step 01: ReactJS Quiz Component

Quiz component will mainly have 3 elements for quiz UI

  1. Question
  2. Answers
  3. Next Button

Lets’s start writing quiz component in Reactjs

We have defined 3 states:

  1. activeQuestions, keep track of current question
  2. selectedAnswer, which answer user has selected
  3. result, to calculate total scores, correctAnswers, wrongAnswers.
const Quiz = () => {
const [activeQuestion, setActiveQuestion] = useState(0)
const [selectedAnswer, setSelectedAnswer] = useState('')
const [result, setResult] = useState({
score: 0,
correctAnswers: 0,
wrongAnswers: 0,
})
return <h1>Quiz</h1>
}
export default Quiz

Step 02: Getting Questions From Question File

In this step, our goal is to display the question from the question file on the screen. Since the question file is an array, we can use an index to access particular array item (question). The code to get the first question is listed below.

Get first question

const Quiz = () => {
const [activeQuestion, setActiveQuestion] = useState(0)
const [selectedAnswer, setSelectedAnswer] = useState('')
const { questions } = quiz
return (
<div>
<h1>Quiz</h1>
<h2>{questions[activeQuestion].question}</p>
</div>
)
}
export default Quiz

Info

Javascript array starts from index 0, so activeQuestion initial state is 0.

Step 03: Fetching Answers

In our quiz question file, each question contains answers too, that is an array so we can render all the answers using the javascript map function

const Quiz = () => {
const [activeQuestion, setActiveQuestion] = useState(0)
const [selectedAnswer, setSelectedAnswer] = useState('')
const { questions } = quiz
// destructuring
const { question, choices } = questions[activeQuestion]
return (
<div>
<h1>Quiz</h1>
<h2>{question}</h2>
<ul>
{choices.map((item) => (
<li>{item}</li>
))}
</ul>
</div>
)
}

Step 04: Moving To The Next Question

On clicking the next button we will evaluate the following things:

  1. Adding one to activeQuestion (to move to the next question)
  2. To check if the selected answer is right or wrong.
  3. Based on selected answer, we are updating the result screen, adding score to the result state, no. of right and wrong answers.
const Quiz = () => {
const [activeQuestion, setActiveQuestion] = useState(0)
const [selectedAnswer, setSelectedAnswer] = useState('')
const onClickNext = () => {
setActiveQuestion((prev) => prev + 1)
}
const { questions } = quiz
const { question, choices } = questions[activeQuestion]
return (
<div>
<h1>Quiz</h1>
<h2>{question}</h2>
<ul>
{choices.map((item) => (
<li>{item}</li>
))}
</ul>
<button onClick={onClickNext}>Next</button>
</div>
)
}
initial result

We haven’t added any styling yet.

onClickNext function we calculate the user score on basis of selected answer.

The use of the onClickNext method is noticeable in the code. Depending on the selected answer determines the user's score. The score will increase by 5, and the number of right answers will increase by one if the chosen option is accurate. If not, it will leave the outcome unchanged and add one to the incorrect responses.

const onClickNext = () => {
setActiveQuestion((prev) => prev + 1)
setResult((prev) =>
selectedAnswer
? {
...prev,
score: prev.score + 5,
correctAnswers: prev.correctAnswers + 1,
}
: { ...prev, wrongAnswers: prev.wrongAnswers + 1 }
)
}

Logic For Selected Answer

As we have discussed above our Javascript questions file contains the answers too for each question.

When the user selects the answer, we will match if the selected answer matches the answer given in question file.

const onAnswerSelected = (answer) => {
if (answer === correctAnswer) {
setSelectedAnswer(true)
console.log('right')
} else {
setSelectedAnswer(false)
console.log('wrong')
}
}
return (
<div className="quiz-container">
<h1>Quiz</h1>
<h2>{question}</h2>
<ul>
{choices.map((answer) => (
<li onClick={() => onAnswerSelected(answer)} key={answer}>
{answer}
</li>
))}
</ul>
<button onClick={onClickNext}>Next</button>
</div>
)

This is what our UI looks like so far but some ingredients are still missing.

result before improving ui ux

Step 05: Performance And UX Improvements

a. We want to highlight the selected answer by changing its UI.

b. In case of last question, we will show Finish as button text instead of Next.

c. Disable the next button if answer is not selected.

d. Adding 0 before question counter on top.

Let's implement them one by one.

a. Highlight the selected answer color

In the above result, you may have noticed we are not having any visuals for selected answer. Let's add this.

For this we have added another state selectedAnswerIndex, so highlight only selected answer.

const [selectedAnswerIndex, setSelectedAnswerIndex] = useState(null)
const onAnswerSelected = (answer, index) => {
setSelectedAnswerIndex(index)
if (answer === correctAnswer) {
setSelectedAnswer(true)
} else {
setSelectedAnswer(false)
}
}
<ul>
{choices.map((answer, index) => (
<li
onClick={() => onAnswerSelected(answer, index)}
key={answer}
className={selectedAnswerIndex === index ? 'selected-answer' : null}>
{answer}
</li>
))}
</ul>

CSS

.quiz-container ul .selected-answer {
background: #ffd6ff;
border: 1px solid #800080;
}

b. Change button title if it is last question

In case of last question change button title to Finish from Next.

<button onClick={onClickNext}>{activeQuestion === questions.length - 1 ? 'Finish' : 'Next'}</button>

c. Disable Next button if answer is not selected

if selectedAnswerIndex is null, means user haven’t clicked any answer, button will be disabled.

<button onClick={onClickNext} disabled={selectedAnswerIndex === null}>
{activeQuestion === questions.length - 1 ? 'Finish' : 'Next'}
</button>

d. Add leading zero

Add a 0 to the question number less than 10.

const addLeadingZero = (number) => (number > 9 ? number : `0${number}`)

Now UI and UX are so much improved.

quiz app design

Code Uptill now

import { useState } from 'react'
import { quiz } from '../../data/questions'
import './quiz.css'
const Quiz = () => {
const [activeQuestion, setActiveQuestion] = useState(0)
const [selectedAnswer, setSelectedAnswer] = useState('')
const [selectedAnswerIndex, setSelectedAnswerIndex] = useState(null)
const [result, setResult] = useState({
score: 0,
correctAnswers: 0,
wrongAnswers: 0,
})
const { questions } = quiz
const { question, choices, correctAnswer } = questions[activeQuestion]
const onClickNext = () => {
// again reset the selectedAnwerIndex, so it won't effect next question
setSelectedAnswerIndex(null)
setActiveQuestion((prev) => prev + 1)
setResult((prev) =>
selectedAnswer
? {
...prev,
score: prev.score + 5,
correctAnswers: prev.correctAnswers + 1,
}
: { ...prev, wrongAnswers: prev.wrongAnswers + 1 }
)
}
const onAnswerSelected = (answer, index) => {
setSelectedAnswerIndex(index)
if (answer === correctAnswer) {
setSelectedAnswer(true)
} else {
setSelectedAnswer(false)
}
}
const addLeadingZero = (number) => (number > 9 ? number : `0${number}`)
return (
<div className="quiz-container">
<div>
<span className="active-question-no">{addLeadingZero(activeQuestion + 1)}</span>
<span className="total-question">/{addLeadingZero(questions.length)}</span>
</div>
<h2>{question}</h2>
<ul>
{choices.map((answer, index) => (
<li
onClick={() => onAnswerSelected(answer, index)}
key={answer}
className={selectedAnswerIndex === index ? 'selected-answer' : null}>
{answer}
</li>
))}
</ul>
<div className="flex-right">
<button onClick={onClickNext} disabled={selectedAnswerIndex === null}>
{activeQuestion === questions.length - 1 ? 'Finish' : 'Next'}
</button>
</div>
</div>
)
}
export default Quiz

Now we are all set to show the result component

Step 06: Result Screen

The result screen will actually a condition based, as soon all the questions finish we will show the result component.

In result screen, we want to show 4 things,

  1. No. of questions
  2. Total Score we get
  3. How many questions were correct
  4. How many questions were wrong

First of all, we will define a showResult state, that will be false initially.

const Quiz = () => {
const [activeQuestion, setActiveQuestion] = useState(0)
const [selectedAnswer, setSelectedAnswer] = useState('')
const [showResult, setShowResult] = useState(false)
const [selectedAnswerIndex, setSelectedAnswerIndex] = useState(null)
.....
.....
Become a better JavaScript developer by practicing only 10–20 minutes a week

Every Tuesday, you'll receive:

  • A JavaScript problem-solving challenge
  • Three conceptual JavaScript MCQs
Subscribe to JS Bytes

Logic to show the result

When all the questions will be finished showResult will become true.

Then we wrote a logic in jsx if showResult is true, show the result in quiz-container otherwise quiz questions.

return (
<div className="quiz-container">
{!showResult ? (
<div>
.... {/* quiz question logic */}
</div>
) : (
<div className="result">
<h3>Result</h3>
<p>
Total Question: <span>{questions.length}</span>
</p>
<p>
Total Score:<span> {result.score}</span>
</p>
<p>
Correct Answers:<span> {result.correctAnswers}</span>
</p>
<p>
Wrong Answers:<span> {result.wrongAnswers}</span>
</p>
</div>
)}
</div>
)
}

We also added a condition on onClickNext function.

const onClickNext = () => {
setSelectedAnswerIndex(null)
setResult((prev) =>
selectedAnswer
? {
...prev,
score: prev.score + 5,
correctAnswers: prev.correctAnswers + 1,
}
: { ...prev, wrongAnswers: prev.wrongAnswers + 1 }
)
if (activeQuestion !== questions.length - 1) {
setActiveQuestion((prev) => prev + 1)
} else {
setActiveQuestion(0)
setShowResult(true)
}
}

Here we are saying when activeQuestion !== questions.length - 1 means it is not the last question, add one to activeQuestion if it is last question showResult will be true.

if you like you can move result jsx to a separate component and pass data via props.

This is how our result component UI will look like.

quiz app result

Complete Component of Reactjs Quiz App

import { useState } from 'react'
import { quiz } from '../../data/questions'
import './quiz.css'
const Quiz = () => {
const [activeQuestion, setActiveQuestion] = useState(0)
const [selectedAnswer, setSelectedAnswer] = useState('')
const [showResult, setShowResult] = useState(false)
const [selectedAnswerIndex, setSelectedAnswerIndex] = useState(null)
const [result, setResult] = useState({
score: 0,
correctAnswers: 0,
wrongAnswers: 0,
})
const { questions } = quiz
const { question, choices, correctAnswer } = questions[activeQuestion]
const onClickNext = () => {
setSelectedAnswerIndex(null)
setResult((prev) =>
selectedAnswer
? {
...prev,
score: prev.score + 5,
correctAnswers: prev.correctAnswers + 1,
}
: { ...prev, wrongAnswers: prev.wrongAnswers + 1 }
)
if (activeQuestion !== questions.length - 1) {
setActiveQuestion((prev) => prev + 1)
} else {
setActiveQuestion(0)
setShowResult(true)
}
}
const onAnswerSelected = (answer, index) => {
setSelectedAnswerIndex(index)
if (answer === correctAnswer) {
setSelectedAnswer(true)
} else {
setSelectedAnswer(false)
}
}
const addLeadingZero = (number) => (number > 9 ? number : `0${number}`)
return (
<div className="quiz-container">
{!showResult ? (
<div>
<div>
<span className="active-question-no">{addLeadingZero(activeQuestion + 1)}</span>
<span className="total-question">/{addLeadingZero(questions.length)}</span>
</div>
<h2>{question}</h2>
<ul>
{choices.map((answer, index) => (
<li
onClick={() => onAnswerSelected(answer, index)}
key={answer}
className={selectedAnswerIndex === index ? 'selected-answer' : null}>
{answer}
</li>
))}
</ul>
<div className="flex-right">
<button onClick={onClickNext} disabled={selectedAnswerIndex === null}>
{activeQuestion === questions.length - 1 ? 'Finish' : 'Next'}
</button>
</div>
</div>
) : (
<div className="result">
<h3>Result</h3>
<p>
Total Question: <span>{questions.length}</span>
</p>
<p>
Total Score:<span> {result.score}</span>
</p>
<p>
Correct Answers:<span> {result.correctAnswers}</span>
</p>
<p>
Wrong Answers:<span> {result.wrongAnswers}</span>
</p>
</div>
)}
</div>
)
}
export default Quiz

CSS

@import url('https://fonts.googleapis.com/css2?family=Anek+Malayalam:wght@100;200;300;400;500;600;700;800&display=swap');
body {
font-family: 'Anek Malayalam', sans-serif;
background: linear-gradient(90.04deg, #800080 0.03%, #ffc0cb 99.96%);
color: #11052c;
display: flex;
justify-content: center;
margin: 0;
padding: 0 30px;
}
.quiz-container {
max-width: 500px;
min-width: 250px;
background: #ffffff;
border-radius: 4px;
margin-top: 100px;
padding: 30px 60px;
}
.quiz-container .active-question-no {
font-size: 32px;
font-weight: 500;
color: #800080;
}
.quiz-container .total-question {
font-size: 16px;
font-weight: 500;
color: #e0dee3;
}
.quiz-container h2 {
font-size: 20px;
font-weight: 500;
margin: 0;
}
.quiz-container ul {
margin-top: 20px;
margin-left: -40px;
}
.quiz-container ul li {
text-decoration: none;
list-style: none;
color: #2d264b;
font-size: 16px;
background: #ffffff;
border: 1px solid #eaeaea;
border-radius: 16px;
padding: 11px;
margin-top: 15px;
cursor: pointer;
}
.quiz-container ul .selected-answer {
background: #ffd6ff;
border: 1px solid #800080;
}
.quiz-container button {
background: linear-gradient(90.04deg, #800080 0.03%, #ffc0cb 99.96%);
border-radius: 9px;
font-size: 18px;
color: #ffffff;
padding: 10px 42px;
outline: none;
border: none;
cursor: pointer;
margin-top: 15px;
}
.quiz-container button:disabled {
background: #e7e8e9;
color: #9fa3a9;
cursor: not-allowed;
}
.flex-right {
display: flex;
justify-content: flex-end;
}
.result h3 {
font-size: 24px;
letter-spacing: 1.4px;
text-align: center;
}
.result p {
font-size: 16px;
font-weight: 500;
}
.result p span {
color: #800080;
font-size: 22px;
}

Apply What You've Learned

This quiz app is missing a timer. You can improve this quiz app by adding a timer. However, I would suggest adding a timer with a twist.

For example, for each question the user will have 7 seconds to answer. If they fail to respond within 7 seconds, the app would automatically proceed to the next question.

Read more details of 7 Second Quiz Challenge to practice what you have learned.

React Quiz App Template

I've developed a Quiz App Template called "Xeven Quiz" to help you get started. This template is designed with ReactJS, Typescript and Styled Components that offers various question types, including Multiple Choice Questions (MCQs), Multiple Answer Questions (MAQs), and True/False questions.

With Xeven Quiz, users can easily take the quiz and see their results. The result screen shows how many questions the user attempted, how much they scored, how long it took, and whether they passed or failed.

In the result screen, users can see which questions they answered correctly and which ones were wrong. If the user provided a wrong answer, they can find the correct answer for that particular question.

To experience the Demo App, visit the link.

The React Quiz app template also has clear code documentation on GitHub. Feel free to check it out and use it as a starting point for your own quiz app project.

Codepen Demo | Reactjs Quiz App

Read Next

Codevertiser Magazine

Subscribe for a regular dose of coding challenges and insightful articles, delivered straight to your inbox