React Quiz with Unlimited Questions
Tags: react, javascript
Published: 2021-12-12 02:02:15

Wouldn't it be awesome if you could create a quiz app that wouldn't require you to write hundreds of questions along with a correct response and multiple incorrect responses? Though this is by no means the only way to make this happen, here is an solution to make it work.

*Before you continue on to find out how, here is the caveat: This method requires the same question each time, and in turn generates multiple buttons with one correct response and all others incorrect. I'll give you an example of how I used this method, and then explain how.*

<h1>My Project</h1>

My 4-year-old son is currently obsessed with world flags, which inspired me to create a React App with information about flags, population, capitals, and continents. After exploring the site, users can take quizzes for each category.

I used the [Rest Countries API](https://restcountries.com/) to get data about each country for each of the categories above.

In order to create a quiz on flags, I used the countries data to create a flag image, along with four buttons, one with the country that corresponds to the flag image, and three other random countries. In another `div` is the 'Next' button.

![Flag Quiz Setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ysosivcz319ydehjywdx.png)  
 
<h3>Data Fetch</h3>

Using your API of choice, make a GET request and save the data, or whichever part of the data that will be useful to you, to a state variable. A regular old variable should work for what we're doing here as well. If you decide to make a state variable, import and run useEffect to make your data fetch, and include an empty dependencies array so that your fetch only runs once. 

```
const [countries, setCountries] = useState([])

useEffect (() => {
  fetch('https://restcountries.com/v3.1/all')
    .then(res => res.json())
    .then(data => {
      const unMemberCountries = data.filter((country) {
        country.unMember === true 
      }
    setCountries(unMemberCountries)
  })
}, [] )

```

<h3>Additional State Variables</h3>

We're going to need a lot more state variables. In my  case, I added seven more. One for the country that is the correct answer, and three incorrect ones, along with state variables to keep track of the question number, score, and whether or not a question has been answered. The four countries are set with a function to choose a random country from the array of countries saved in the countries variable.

```
 const [flagQuizCountry, setFlagQuizCountry] = useState(countries[Math.floor(Math.random()*countries.length)])
  const [incorrectFlagOne, setIncorrectFlagOne] = useState(countries[Math.floor(Math.random()*countries.length)])
  const [incorrectFlagTwo, setIncorrectFlagTwo] = useState(countries[Math.floor(Math.random()*countries.length)])
  const [incorrectFlagThree, setIncorrectFlagThree] = useState(countries[Math.floor(Math.random()*countries.length)])
  const [flagResponseGiven, setFlagResponseGiven] = useState(false)
  const [currentFlagQuestion, setCurrentFlagQuestion] = useState(0)
  const [flagQuizScore, setFlagQuizScore] = useState (0)
```

<h3>Create your buttons</h3>

In your JSX, create four buttons. Each button should have its value set to equal the text inside the button. So if I'm asking "Which country's flag is this?" I have four buttons that are set to my state variables: flagQuizCountry (correct answer), incorrectOne, incorrectTwo, and incorrectThree. Then in a following `div`, add your 'Next' button.

```
<div'>
  <ul style={{display: 'flex', flexDirection: 'row', justifyContent: 'center'}} 
    <li>
      <button 
        value={flagQuizCountry.name}
        onClick={handleFlagAnswer} > 
        {flagQuizCountry.name} 
      </button>
    </li>
    <li>
      <button 
        value={incorrectOne.name}
        onClick={handleFlagAnswer} > 
        {incorrectOne.name} 
      </button>
    </li>
    <li>
      <button 
        value={incorrectTwo.name}
        onClick={handleFlagAnswer} > 
        {incorrectTwo.name} 
      </button>
    </li>
    <li>
      <button 
        value={incorrectThree.name}
        onClick={handleFlagAnswer} > 
        {incorrectThree.name} 
      </button>
    </li>
  </ul
</div>
<div>
  <button onClick={flagOnClick}>Next</button>
</div>
```
And here comes someone else creative solution that I found to randomize the button order. My gratitude to the wise thinker who posted this solution [here](https://techwelkin.com/randomize-html-list-display-html-list-items-randomly). Create each of your buttons inside a `li` tag, and place all the buttons inside a `ul` tag. Then use this code to randomize the order of the items in the list.

```
function randomizeItems(items) {
  let cached = items.slice(0), temp, i = cached.length, rand;
  while(--i) {
    rand = Math.floor(i * Math.random());
    temp = cached[rand];
    cached[rand] = cached[i];
    cached[i] = temp;
  }
  return cached;
}

function randomizeList() {
  let list = document.getElementById("capital-quiz-buttons");
  let nodes = list.children, i = 0;
  nodes = Array.prototype.slice.call(nodes);
  nodes = randomizeItems(nodes);
  while(i < nodes.length) {
    list.appendChild(nodes[i]);
    ++i;
  }
}
```

Style your buttons as you see fit, but make sure that you style your `ul` in the following fashion to take away the bullet points.

```
ul {
  list-style-type: none;
  padding: 0;
}
```

Add an `onClick` event to each button which calls something similar to the following function.

```
function handleFlagAnswer (event) {
    if (flagResponseGiven === false) {
      if (event.target.value === flagQuizCountry.name.common) {
        setFlagQuizScore(flagQuizScore + 1)
      } 
    setFlagResponseGiven(true)
    }
  }
```
The function will only run if no response has been given yet, and we will set the responseGiven to true onClick. If the button's value equals the `flagQuizCountry`, a point will be added the `flagQuizScore`.

Now we need a function that will run when we click the 'Next' button. We'll set each of our four buttons to a new random country. We'll increase the 'currentFlagQuestion' by one, reset 'flagResponseGiven' to false, and set limits for however many questions we want on the quiz, and then decide what you want to do once the end has been reached.

```
function setNextFlagQuestion () {
  setFlagQuizCountry (countryData[Math.floor(Math.random()*countryData.length)])
  setIncorrectFlagOne (countryData[Math.floor(Math.random()*countryData.length)])
  setIncorrectFlagTwo (countryData[Math.floor(Math.random()*countryData.length)])
  setIncorrectFlagThree (countryData[Math.floor(Math.random()*countryData.length)])
  
  let nextQuestion = currentFlagQuestion + 1
  setCurrentFlagQuestion(nextQuestion)
  setFlagResponseGiven(false)
  resetButtonColors()
  if (currentFlagQuestion >= 25){
      // insert desired outcome
  }
}
```

If you have a quiz that can use the same question each time, you now have a limitless number of randomized quiz questions. 

Photo by <a href="https://unsplash.com/@lamunix?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Ana Municio</a> on <a href="https://unsplash.com/s/photos/question?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>