Search justacoding.blog. [Enter] to search. Click anywhere to close.

December 1st, 2021

React Quiz App Example – A Tutorial

This article covers the creation of a simple React quiz app that should prove useful to entry-level React developers.

You can view the quiz app here in JSFiddle.

The quiz app is about as basic as it can get in terms of functionality, but it provides a solid foundation on which to experiment or to add more features if required.

How it works, in a nutshell

In summary, the main quiz component receives an array of questions. The user must answer each question in turn until they reach the end of the quiz. The user is notified of a correct/incorrect answer for each and every question that they answer.

Let’s talk through the main aspects of this React quiz app example and explain how it all works in more detail!

The question data structure

The main Quiz component receives an array of questions. The questions are structured as follows:

{
  question: "In what contintent is Indonesia?",
  answers: ["South America", "Europe", "Asia"],
  correctAnswerIndex: 2
}

So there is the question text itself, the possible answers as well as an index (correctAnswerIndex) which represents the correct answer for this particular question.

These are the only aspects that the quiz questions require, so let’s move on to outputting the questions for the user!

Displaying the questions

In this particular example, Quiz is the main component. This component tracks the various states required by the quiz, so things like the index of the current question, the completion status of the quiz and so on.

The questions are actually output by a dedicated component, Question:

<Question 
  question={questions[questionIndex]} 
  setAnswerStatus={setAnswerStatus}
/>

A single question is passed to this component, selected by it’s index within the supplied array. This is the question which is currently active within the quiz.

This Question component is responsible for two things: it outputs the specific (active) question, as well as provides the facility for the user to select an answer to this question.

Upon answering a specific question, the user is then provided with the facility to advance forward to the next question. We’ll look at that aspect of the functionality in a moment.

Iterating through the quiz questions

To iterate through the quiz questions, it’s necessary to have a value in state to track the index of the “current” question.

In this case, the variable I am using is named questionIndex.

const [questionIndex, setQuestionIndex] = useState(null)

This value starts at null rather than 0 as you may expect. If questionIndex is null, I know that the quiz has not yet started — so I’m showing the intro screen (with an option to actually start the quiz), instead.

Every time the user should advance to the next question, it’s a matter of incrementing this questionIndex by 1. Or if the user has opted to start the quiz – setting questionIndex to 0 from null. This will activate the very first question.

We can use the setQuestionIndex function to do this in each case.

setQuestionIndex(questionIndex == null ? 0 : questionIndex + 1)

If the index value is null, we know to set it to 0 to select the first question. This is effectively how we “start” the quiz (present the questions) for the user.

Once the user has iterated through each and every question, we know the quiz has ended. So we can react to this scenario and show the appropriate screen, too. More on this bit later on.

Answering questions

To answer a question, we need to react to the user input. More specifically, whenever the user clicks an answer for a given question, we must compare the index of this question with the index of the “correct” answer — denoted by correctAnswerIndex.

useEffect(() => {
  if (selectedAnswerIndex != null) {
    setAnswerStatus(selectedAnswerIndex === question.correctAnswerIndex)
  }
}, [selectedAnswerIndex])

If we have a match on these two indexes, we know that the user answered this specific question correctly.

If there’s no match, we know that the user instead selected one of the incorrect answers.

In this case, I’m passing back the results of this answer status (true or false) to the parent Quiz component using setAnswerStatus.

The Quiz component needs to know this as this component is responsible for controlling the overall state of the game. If we inform this component that a question has been answered, the component knows to navigate forward (or at least, to present the option for the user to do so).

If answerStatus is null, we know the question has not yet been answered.

If it’s true or false, we know that the user has supplied an answer.

useEffect(() => {
  if (answerStatus) {
    setCorrectAnswerCount(count => count + 1)
  }
}, [answerStatus])

In this example, we’re maintaining a count of true (or correct) answers via the correctAnswerCount value. This will allow us to report on this statistic later on, after the quiz has ended.

Indicating correct or incorrect answers via the UI

You’ll notice I’m conditionally applying certain classes to each of the answer elements:

{question.answers.map((answer, index) => {
  return (
    <div key={index} 
      className={`answer ${getClasses(index)}`} 
      onClick={() => selectedAnswerIndex == null && setSelectedAnswerIndex(index)}>
        {answer}
    </div>
  )
})}

These CSS classes are used to flag or highlight the user’s specific answer in each case, as well as indicate the correct answer once the user has provided their input.

It’s simply a matter of comparing the selected answer index with the correct answer index in order to accomplish what’s required here.

react quiz example app
The user selected correctly!

There are 4 possible states for each answer element here:

  • An unselected answer, so the default state
  • A selected answer, the answer that the user has clicked on (grey)
  • A correct answer that the user selected (green)
  • A correct answer that the user didn’t select (red)

This type of feedback provides important information to the user, and it’s also somewhat aesthetically pleasing too, which helps.

Showing the “Next” button

Of course, once the user has answered a question – they will need a means to advance forward to the next question.

This could be an automatic thing. But in this particular quiz app example, the user must manually select “Next” in order to progress.

This gives them time to verify the correct answer each time.

As explained previously, we already have a means to iterate through the quiz questions. So it’s a matter of using this here.

Once we know the question has been answered, so once selectedAnswerIndex is not null within this component, we can use setAnswerStatus no notify the parent Quiz component of this.

The parent component is then responsible for actually showing the “Next” button, as required.

{answerStatus != null && (
  <div>
    // ...
    <button 
      className="next" onClick={onNextClick}>
      {questionIndex === questions.length - 1 ? 
        "See results of this quiz" : "Next Question ->"}
    </button>
  </div>
)}

Tracking progress

You’ll notice I’m also using a component called ProgressBar to track the user’s progress within this React quiz app implementation.

It’s handy to have an indicator of how many questions there are in total within the quiz, as well as how far through the quiz the user has actually progressed so far. That’s what this component is responsible for.

Making the progress bar element

The progress bar itself is a horizonal bar which displays the user’s progress in a percentage-based fashion.

To construct this element, it’s firstly necessary to determine out how far through the quiz the user actually is, and then fill the bar in accordance with this.

We already have the necessary components to do this.

So we can use the length of the questions array supplied, questions.length. And the index that represents the current active question within the quiz, questionIndex.

Based on this, a simple calculation is all we need, along with some basic styling to accomplish the final effect:

const ProgressBar = ({ currentQuestionIndex, totalQuestionsCount }) => {
  const progressPercentage = (currentQuestionIndex / totalQuestionsCount) * 100
  
  return (
    <div className="progressBar">
      <div className="text">
        {currentQuestionIndex} answered 
        ({totalQuestionsCount - currentQuestionIndex} remaining)
      </div>
      <div className="inner" style={{ width: `${progressPercentage}%` }} />
    </div>
  )
}

Now the progress bar “fills up” with each question as the user navigates through the quiz!

Completing the quiz

The quiz is complete once the user has answered every question.

To measure completion, we can compare the index of the current question with the total amount of questions:

const onNextClick = () => {
  if (questionIndex === questions.length - 1) {
    setQuizComplete(true)
  } else {
    setQuestionIndex(questionIndex == null ? 0 : questionIndex + 1)
  }
}

Once the current question index reaches (length of total questions - 1), we know that the user has answered every question, and thus, we should show the “Quiz complete!” screen instead of a fresh question:

Presenting some stats to the user

It’s a nice touch to provide some feedback to the user at this point.

In this case, I’m showing the total amount of correct questions compared to the total amount of incorrect questions.

These correct answers are tracked during the answering functionality, whereby a simple value is maintained in state after every answer:

<Fragment>
  <h1>Quiz complete!</h1>
  <p>
    You answered {correctAnswerCount} questions correctly 
    (out of a total {questions.length} questions)
  </p>
</Fragment>

Restarting the quiz

Restarting the quiz is as simple as resetting three of the relevant values: quizComplete, questionIndex and correctAnswerCount:

const onRestartClick = () => {
  setQuizComplete(false)
  setQuestionIndex(null)
  setCorrectAnswerCount(0)
}

So as you can see here, resetting the React quiz app in its entirety is a matter of modifying all of the relevant items in state back to their initial values.

We’re simply navigating back to the initial starting screen, and also marking the quiz as incomplete so we no longer show the “Quiz complete!” screen anymore. The correct answer count is reset too, we need that to start at 0 every time the user runs through the quiz!

In summary

I hope you’ve enjoyed this article and I hope that the provided React quiz app example has been of some use or benefit!

As stated, the quiz is simple in it’s functionality but it could be an ideal base upon which to experiment and add further features.

Some examples of this would be:

  • A timer. Impose time limits per question, or measure the overall time taken to complete the quiz and report on this upon completion
  • Supply a quiz object instead of just an array of questions, this could contain the quiz name, difficult rating and any other pieces of information that may be useful
  • Image-based quiz questions
  • Fetch quiz questions from a backend service or API instead of using the example hard-coded ones

I’m sure there are many more avenues to explore, too.

More code examples

Please feel free to check out justacodingblog’s other code examples.

The components and applications contained within this category are similar to this quiz app. Mainly simple builds with an accompanying article each.

Thanks for reading!

Have any questions? Ping me over at @justacodingblog
← Back to blog