DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for React Quiz with Unlimited Questions
Christopher Ninman
Christopher Ninman

Posted on • Updated on

React Quiz with Unlimited Questions

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.

My Project

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 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

Data Fetch

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)
  })
}, [] )

Enter fullscreen mode Exit fullscreen mode

Additional State Variables

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)
Enter fullscreen mode Exit fullscreen mode

Create your buttons

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>
Enter fullscreen mode Exit fullscreen mode

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. 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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
  }
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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 Ana Municio on Unsplash

Top comments (0)

An Animated Guide to Node.js Event Lop

Node.js doesn’t stop from running other operations because of Libuv, a C++ library responsible for the event loop and asynchronously handling tasks such as network requests, DNS resolution, file system operations, data encryption, etc.

What happens under the hood when Node.js works on tasks such as database queries? We will explore it by following this piece of code step by step.