How to Create a Practice Quiz using React
Tags: webdev, react, quiz, usestate
Published: 2023-03-11 18:35:08

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](https://github.com/abbyesmith/Algebra-1-Course-Review-Phase-2-Project.git) and a quick walk through demo of my project. 
{% embed https://www.loom.com/share/9dde9e137c4c409eb806a464b76ee535 %}
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](https://reactjs.org/docs/create-a-new-react-app.html#create-react-app) 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;
```

Here's a quick snip of what should be rendering on our page:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nz2fkngdzg0gdfs0nnmp.png)

**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 },
      ]
    }
  ]
  
```
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);

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

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.
{% embed https://www.loom.com/share/d769917feffa4c35bd3651aa1ede7b00 %}

**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);
```
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)
    } 
}
```
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;
```
Congrats! Users can progress through your quiz and see feedback as they answer the questions. Compare your React app progress with the video below. 
{% embed https://www.loom.com/share/24ebac493fb64b02911c0ed9dbe7fd47 %}

**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);
```
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)
    }
```
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;
```

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.
{% embed https://www.loom.com/share/d803918583d8421fabc1b60816a2b110 %}

**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);
};
```
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>
      ) : ( 
```
WooHoo!! You have a fully functioning app!! Make sure your app is functioning the video below:
{% embed https://www.loom.com/share/fdaf6f17a5134c60a9abbb5a37ffed56 %}

**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"
}
```
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>
      ) : ( 
```
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!