DEV Community

abbyesmith
abbyesmith

Posted on • Edited on

How to Create a Practice Quiz using React

Hey Friends! It's your local teacher turned coder checking in as my bootcamp phase covering React is coming to a close. I'd like to show you how to make a simple practice quiz with automatic scoring on your next project.

For reference, I created this practice quiz for my phase 2 project at Flatiron School. Here is the link to the project GitHub Repo and a quick walk through demo of my project.


You'll see the practice quiz we are going to walk through in this post at the end of the walk through. If you are thinking about enrolling in a software bootcamp, feel free to drop some questions in the comment box below!

Part 0 - Create a new create-react-app

Feel free to skip this portion if you plan on adding the code to an already existing project or are comfortable creating a new React app.

Use these directions to create a new React app.

Open your code in your text editor (such as VS Code).

Remove the existing code in the App.js, App.css, etc.

Part 1 - Render an Example Problem

To start, let's create the code to have an example problem render on the screen.

import React from 'react';
import "./App.css"

function App() {
  return (
    <div>
      <h1>Practice Quiz πŸ€”</h1>
      <h3>Current Correct Answers: 2 out of 3</h3>
      <h3>Question 4 out of 5</h3>
      <h2>Who wrote the musical "Hamilton"?</h2>
      <div>
        <button>Lin Manuel Miranda</button>
        <button>Stephen Sondheim</button>
        <button>Rodgers and hammerstein</button>
        <button>James Gunn</button>
      </div>

    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Here's a quick snip of what should be rendering on our page:
Image description

Part 2 - Build out a Question Bank

If you are interested in using a db.json file, you can build out your question bank there and complete a fetch.

In order simplify the skills needed, we'll opt to create our question bank as a variable in our App function.

function App() {
  const questions = [
    {
      questionText: 'Which musical defies gravity?',
      id: 1,
      options: [
        { id: 0, text: 'Annie', isCorrect: false },
        { id: 1, text: 'Book of Mormon', isCorrect: false },
        { id: 2, text: 'Wicked', isCorrect: true },
        { id: 3, text: 'Hade\'s Town', isCorrect: false },
      ],
    },
    {
      questionText: 'Which movie explores the power of the union?',
      id: 2,
      options: [
        { id: 0, text: 'Six', isCorrect: false },
        { id: 1, text: 'Anastasia', isCorrect: false },
        { id: 2, text: 'Newsies', isCorrect: true },
        { id: 3, text: 'The Phantom of the Opera', isCorrect: false },
      ],
    },
    {
      questionText: 'Which band wrote an album intended to be an American musical?',
      id: 3,
      options: [
        { id: 0, text: 'Pink Floyd', isCorrect: false },
        { id: 1, text: 'Simon and Garfunkle', isCorrect: false },
        { id: 2, text: 'Green Day', isCorrect: true },
        { id: 3, text: 'One Direction', isCorrect: false },
      ],
    },
    {
      questionText: 'Who wrote the musical \"Hamilton\"?',
      id: 4,
      options: [
        { id: 0, text: 'Annie', isCorrect: false },
        { id: 1, text: 'Book of Mormon', isCorrect: false },
        { id: 2, text: 'Wicked', isCorrect: true },
        { id: 3, text: 'Hadestown', isCorrect: false },
      ],
    },
    {
      questionText: 'Which musical follows a famous Greek myth?',
      id: 5,
      options: [
        { id: 0, text: 'Hadestown', isCorrect: true },
        { id: 1, text: 'Book of Mormon', isCorrect: false },
        { id: 2, text: 'Wicked', isCorrect: false },
        { id: 3, text: 'Life Of Pi', isCorrect: false },
      ]
    }
  ]

Enter fullscreen mode Exit fullscreen mode

You may add as many questions as you like. Make sure to update the question ID as you go.

Part 3 - Map through the Question Bank

As we map through our data, we are only going to show one question at a time. This means we'll need to useState to set the current question. Create a const as shown in the code block below. Set the initial state to 0 to have the first question generate upon loading the page.

import React, {useState} from 'react';
import "./App.css"

function App() {
  const questions = [
   //Question Bank Here//
      ]
    }
  ]

  const [currentQuestion, setCurrentQuestion] = useState(0);

Enter fullscreen mode Exit fullscreen mode

We'll want to process through our questions when an option choice is clicked. In order to do this, we'll need to create a handleAnswerOptionClick function. We'll keep adding to this function as we build out our code. For now, we'll want to go to the next question as long as there is another question in the bank, as show in the code currentQuestion + 1 < questions.length.

  const handleAnswerOptionClick = () => {
    if (currentQuestion + 1 < questions.length) {
        setCurrentQuestion (currentQuestion + 1);
    } 
}
Enter fullscreen mode Exit fullscreen mode

Now it's time to make our return dynamic. Replace the question text in the <h2> tags with {questions[currentQuestion].questionText}. Only the current question will be rendered because [currentQuestion] is targeting a specific object from our question array.

In order to show all of the answer choices, map through questions[currentQuestion].options.

To be able to click answer choices and progress to the next question, call our handleAnswerOptionClick function.

  return (
    <div className = "problem">
      <h1>Practice Quiz πŸ€”</h1>
      <h3>Current Correct Answers: 2 out of 3</h3>
      <h3>Question 4 out of 5</h3>
      <h2>{questions[currentQuestion].questionText}</h2>
      <div>
        {questions[currentQuestion].options.map((option) => {
          return(
            <span>
              <button key = {option.id} onClick = {() => handleAnswerOptionClick()}>
                {option.text}
              </button>
            </span>
          )
        })}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Check out your React app! You should be able to progress through your questions now. When you reach the end of your questions, you will not be able to progress further.

Part 4 - Scoring

If you are able to click through your questions at this point, AWESOME JOB! If something is breaking, stop now and go back through your code. Often times a missing bracket or misspelled word can break an entire project. Once your app matches the video from part 3, continue to add a scoring ability to your code.

First, set some new states. const [id, setId] = useState(0) will allow us to reference which question # we are on. We'll start at 0 to reference the first object in our question bank array.

The next new state will be const [score, setScore] = useState(0). This will set our initial score as 0.

  const [id, setId] = useState(0);

  const [score, setScore] = useState(0)

  const [currentQuestion, setCurrentQuestion] = useState(0);
Enter fullscreen mode Exit fullscreen mode

Let's now update the handleAnswerOptionClick function to add one to the score if the user selects the correct answer.

We are using the setId state in order to keep track which question number the user is on.

  const handleAnswerOptionClick = (isCorrect) => {
    if (isCorrect){
      setScore(score + 1)
    }
    if (currentQuestion + 1 < questions.length) {
        setCurrentQuestion (currentQuestion + 1);
        setId (id + 1)
    } 
}
Enter fullscreen mode Exit fullscreen mode

Now move onto the return to make sure the screen renders the correct values.

Inside the first <h3> tags, notice that we are adding {score} and {id} to let the user know how many they have answered corrected out of the number attempted. The second set of <h3> tags are allowing the user to see their progress through the quiz.

Moving down, we'll need to pass option.isCorrect to the handleAnswerOptionClick function in order for our function to know to read the part of our question bank where the correct answer was identified with isCorrect.

  return (
    <div className = "problem">
      <h1>Practice Quiz πŸ€”</h1>
      <h3>Current Correct Answers: {score} out of {id}</h3>
      <h3>Question {currentQuestion +1} out of {questions.length}</h3>
      <h2>{questions[currentQuestion].questionText}</h2>
      <div>
        {questions[currentQuestion].options.map((option) => {
          return(
            <span>
              <button key = {option.id} onClick = {() => handleAnswerOptionClick(option.isCorrect)}>
                {option.text}
              </button>
            </span>
          )
        })}
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Congrats! Users can progress through your quiz and see feedback as they answer the questions. Compare your React app progress with the video below.

Part 5 - Result Screen

People love getting immediate feedback on their performance. We are going to add a result screen that shows the user their score on the quiz.

In order to calculate the grade as a percent, use the line of code let grade = (score / questions.length)*100;.

The results page will be called only when the user is at the end of the question bank. We'll control that in our code by using const [showResults, setShowResults] = useState(false). showResults is currently set to false because we do not want the results page to render when the page is loaded.

  const [id, setId] = useState(0);

  const [score, setScore] = useState(0)
  let grade = (score / questions.length)*100;

  const [showResults, setShowResults] = useState(false)

  const [currentQuestion, setCurrentQuestion] = useState(0);
Enter fullscreen mode Exit fullscreen mode

The handleAnswerOptionClick function is going to get even more powerful! Add an else option at the end of the function to change the value of setShowResults to allow the result page to render once the user has clicked through the entire quiz.

  const handleAnswerOptionClick = (isCorrect) => {
    if (isCorrect){
      setScore(score + 1)
    }
    if (currentQuestion + 1 < questions.length) {
        setCurrentQuestion (currentQuestion + 1);
        setId (id + 1)
    } 
    else {
      setShowResults(true)
    }
Enter fullscreen mode Exit fullscreen mode

Move onto the return to build out the results page.

First, we'll create a ternary statement if showResults is true, then the result page will render. If showResults is false, then the questions will render.

Inside the true aspect of showResults, create a header titled something similar to "Your Results". {score} is from our state from earlier. {grade} references the previous variable that converts the score to a percent.

Inside the false aspect of showResults, insert our previously written code for the questions.

  return (
    <div className = "problem">
      <h1>Practice Quiz πŸ€”</h1>
      {showResults ? (
        <div>
          <div>
            <h2>Your Results</h2>
            <h3>Score: {score} out of {questions.length} </h3>
            <h4>Grade: {grade}%</h4>
          </div>
        </div>
      ) : ( 
        <div>
          <h3>Current Correct Answers: {score} out of {id}</h3>
          <h3>Question {currentQuestion +1} out of {questions.length}</h3>
          <h2>{questions[currentQuestion].questionText}</h2>
          <div>
            {questions[currentQuestion].options.map((option) => {
              return(
                <span>
                  <button key = {option.id} onClick = {() => handleAnswerOptionClick(option.isCorrect)}>
                    {option.text}
                  </button>
                </span>
              )
            })}
          </div>
      </div>
  )}
  </div>
  )
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Nice! You have a quiz that a user can take once and receive immediate feedback! Make sure your app functions similarly to the video below.

Part 6 - Allow for Retakes

Do you want your user to be able to try the quiz more than once without having to reload the page? We can do just that with a few additional lines of code.

Create a const restartQuiz to reset our states to 0 or false so the count resets and the user is pushed back to the question set.

const restartQuiz = () => {
  setScore(0);
  setCurrentQuestion(0);
  setShowResults(false);
  setId(0);
};
Enter fullscreen mode Exit fullscreen mode

Call restartQuiz at the end of the results page on a new "Try Again" button.

  return (
    <div className = "problem">
      <h1>Practice Quiz πŸ€”</h1>
      {showResults ? (
        <div className = "final-results">
        <div className= "results">
            <h2 >Your Results</h2>
            <h3>Score: {score} out of {questions.length} </h3>
            <h4>Grade: {grade}%</h4>
        </div>
        <button onClick = {() => restartQuiz()}>Try Again</button>

    </div>
      ) : ( 
Enter fullscreen mode Exit fullscreen mode

WooHoo!! You have a fully functioning app!! Make sure your app is functioning the video below:

Part 7 - Add Memes to the End Result
If you would like to add a meme that corresponds with the user's score, here's a quick way to make that happen.

Create an if/else statement with the variable resultImage. Pick out various memes online or use the ones I have in the code below.

let resultImage
if (grade > 89) {
    resultImage = "https://sayingimages.com/wp-content/uploads/you-did-good-job-meme.jpg"
} else if (grade > 69){
    resultImage = "https://ih1.redbubble.net/image.2500239448.7314/st,small,507x507-pad,600x600,f8f8f8.jpg"
} else if (grade > 49){
    resultImage ="https://cdn.memes.com/up/95499481632781021/i/1633035612375.jpg"
} else {
    resultImage = "https://img.memegenerator.net/instances/54883911.jpg"
}
Enter fullscreen mode Exit fullscreen mode

Call resultImage in the results portion of your code by adding an image tag with the source set as resultImage.

  return (
    <div className = "problem">
      <h1>Practice Quiz πŸ€”</h1>
      {showResults ? (
        <div className = "final-results">
        <div className= "results">
            <h2 >Your Results</h2>
            <h3>Score: {score} out of {questions.length} </h3>
            <h4>Grade: {grade}%</h4>
            <img src = {resultImage} alt = "meme"/>
        </div>
        <button onClick = {() => restartQuiz()}>Try Again</button>

    </div>
      ) : ( 
Enter fullscreen mode Exit fullscreen mode

Your result page should now render a the meme based on the quiz score.

This is where I will leave you to use CSS to style your quiz to fit in with your site's theme.

Happy Coding!

Top comments (0)