How to create a simple Math quiz app
Tags: vue, javascript, webdev, tutorial
Published: 2018-10-23 15:00:25

---
title: How to create a simple Math quiz app
published: true
description: A math quiz web app made in Vue.js
tags: vue, javascript, webdev, tutorial
---

In this article, we will be building a simple quiz web app. It will be made in Vue.js. 

It will be a simple flashcard format. A card will have a question and some options. If the option selected is the correct answer, the card will flip over and congratulate us. For this effect, we will be making use of some transition effects. The end result will look like this.

![Final result of the quiz app - question and options](https://thepracticaldev.s3.amazonaws.com/i/cc7uglnxf7szvu7nrf46.JPG)

![Final result of the quiz app - correct answer](https://thepracticaldev.s3.amazonaws.com/i/v7k2zdwx2h366eiftl9g.JPG)

---

First, let's set up our files. Open your terminal, go to the project folder and type the following commands at the terminal. You may select any name for your project. You won't be needing *Vuex* or *Vue-router* so don't choose them. The rest of the options are up to you, you can select the ones you want.

```bash
#for Vue 3.x
vue create quiz-app

#for Vue 2.x (legacy version)
vue init webpack quiz-app
```

Our initial files are ready. Open your favorite code editor/IDE and let's get started.

From the boilerplate code, delete the `HelloWorld.vue` component since we won't be needing it. One of the main components will be the `App.vue` component. There is a very simple structure for flashcard-based quizzes. There is a question with some options (usually 4) on one side and the answer on the other side. Thus we can put our questions with options to a separate component called `Question.vue` and put the answer in a separate one called, you guessed it, `Answer.vue`.

Let's start with `App.vue` and set up the basic structure of our app. I'll be using Bootstrap v4 in this project. You may use it or any other library that you are familiar with.

```html
<template>
  <div class="container">
    <div class="row">
      <div class="col-sm">
        <h1 class="text-center">
          The Most Awesome Quiz
        </h1>
      </div>
    </div>
    <hr>
    <div class="row">
      <div class="col-sm">
        <transition name="flip" mode="out-in">
          <component :is="mode" @answered="answered($event)" @confirmed="mode = 'Question'"></component>
        </transition>
      </div>
    </div>
  </div>
</template>
```

We have our heading *The Most Awesome Quiz*. Then there's a `<transition>` tag with some attributes. If you're not familiar with the *transition* tag, it's something that Vue provides us. It allows us to apply transitions to anything by simply wrapping the element with a `<transition>` tag. Our tag has two attributes - **name** is the name of the transition and `mode="out-in"` tells Vue to wait until the previous transition has completely finished before starting a new one.

Inside we have another Vue-provided tag called `<component>`. This is used for dynamic components. 

The basic structure will be like this - we have a dynamic component that will be initially always set to show the `Question` component. When we select the correct answer among the options, it will switch the component to `Answer`. This is possible with dynamic components. And we can apply transitions while switching between components thanks to the `<transition>` tag.

As for the attributes of our `<component>` tag. The last two are `v-on`s we are using for custom events. `@answered` will be a custom event generated by the `Question` component. It will tell us whether the answer that was selected was the correct one. We can then choose what to do. `@confirmed` is the one attached to the `Answer` component. What it does is switch back to the `Question` component and show a new question.

The first attribute `:is` is needed for switching between dynamic components. It is a dynamic attribute itself since it will need to change its value.

The rest of the code in the template is just Bootstrap used to add visual goodness to the page so it doesn't look like something from the earlier days of the Internet.

Now comes the core logic for this component. It's quite small (though not as small as the logic for `Answer`).

```javascript
import Question from './components/Question.vue';
import Answer from './components/Answer.vue';
export default {
  data() {
    return {
      mode: 'Question'
    }
  },
  components: {
    Question,
    Answer
  },
  methods: {
    answered(isCorrect) {
      if (isCorrect) {
        this.mode = 'Answer';
      } else {
        this.mode = 'Question';
        alert("That's the wrong answer! Try again.")
      }
    }
  }
}
```

First, we import the two components that we will create. I have put them in a separate `/components` folder. 

We have only one data attribute which will be used to switch between both components dynamically. The only method is used for taking a particular action depending on whether the correct option was picked. 

Note that we don't decide whether the correct answer was selected. That is done by the `Question` component. We simply act on it. If the `Question` component says that the correct answer was selected, we switch to the `Answer` component and if it was wrong, we show an alert.

Now that the template and the core logic is done let's quickly finish the transition effects.

```css
.flip-enter-active{
  animation: flip-in 0.5s ease-out forwards;
}
.flip-leave-active{
  animation: flip-out 0.5s ease-out forwards;
}
@keyframes flip-out{
  from{
    transform: rotateY(0deg);
  } 
  to {
    transform: rotateY(90deg);
  }
}
@keyframes flip-in {
  from {
    transform: rotateY(90deg);
  }
  to {
    transform: rotateY(0deg);
  }
}
```

The classes `.flip-enter-active` and `.flip-leave-active` are also provided by Vue when we gave the transition a name (Vue *does* give us so many nice things). The first class is used when the transition is in the *enter* stage, meaning it's beginning. The second class is applied when the transition is *actively leaving* or wrapping up.

You'd be better off seeing the `@keyframes` in action rather than me explaining it. It has [this effect](https://jsfiddle.net/NAPOLEON039/1058jvd2/1/)

---

The `Answer` component doesn't contain much code since all it does is show a congratulatory message.

```html
<template>
    <div class="alrt alert-success text-center">
        <h1>That's the correct answer!!</h1>
        <hr>
        <button class="btn btn-primary" @click="onNextQuestion">Next Question</button>
    </div>
</template>
```

The template is quite easy to understand. Just an `<h1>` and a button that shows the next question.

```javascript
methods: {
	onNextQuestion() {
		this.$emit('confirmed');
	}
}
```

The method is invoked by clicking the button and emits a *confirmed* event to the parent `App.vue` component. If you remember, when this event is emitted, the `App` component switches to the `Question` component.

---

Now for the final and probably the longest of the 3 components. The `Question` component has more logic than the previous components since it handles the crucial task of creating new questions and determining whether the correct answer was selected.

```html
<template>
    <div class="container text-center">
        <div class="card">
            <div class="card-body">
                <h3 class="card-title text-center">{{question}}</h3>
                <div class="card-text">
                    <div class="row">
                        <div class="col-sm">
                            <button class="btn btn-primary btn-lg" style="margin: 10px" @click="onAnswer(btnData[0].correct)"> {{btnData[0].answer}} </button>
                        </div>
                        <div class="col-sm">
                            <button class="btn btn-primary btn-lg" style="margin: 10px" @click="onAnswer(btnData[1].correct)"> {{btnData[1].answer}} </button>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col-sm">
                            <button class="btn btn-primary btn-lg" style="margin: 10px" @click="onAnswer(btnData[2].correct)"> {{btnData[2].answer}} </button>
                        </div>
                        <div class="col-sm">
                            <button class="btn btn-primary btn-lg" style="margin: 10px" @click="onAnswer(btnData[3].correct)"> {{btnData[3].answer}} </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
```

It seems overwhelming, but it really isn't. A major part of it is Bootstrap. This template displays a card (available in Bootstrap v4) with a simple addition or subtraction question. The numbers are random and we have also randomized the operation. So whether the next question would be an addition or a subtraction is also random.

Next, we have 4 buttons. These will be our options to the question. One of them will be the correct answer (the position of the correct answer also changes randomly by the way). Quite a lot of randomness 😉. But that's what makes this fun!

It would become clear what the interpolated string does after understanding the rest of the code.

```javascript
const MODE_ADDITION = 1;
const MODE_SUBTRACTION = 2
export default {
    data() {
        return {
            question: 'Oops, an error occured :/',
            btnData: [
                {correct: true, answer: 0},
                {correct: false, answer: 0},
                {correct: false, answer: 0},
                {correct: false, answer: 0}
            ]
        }
    }
}
```

We have two variables to indicate the mode of operation. The `btnData` is an array of objects. Each object represents an *answer*. It has two properties - **answer** is the answer that the button represents. Each button will have an answer which may or may not be correct. This property will contain that. **correct** will tell us whether that answer is correct or not. 

Even though the *correct* property of the first object is set to `true`, it will be changed later.

The `question` data property will have a string by default. So if our method that generates a question somehow doesn't work, we would know something is wrong.

```javascript
created() {
	this.generateQuestion();
},
```

Next, we have this `created()` lifecycle hook. When this component is created, the `generateQuestion()` method would be executed. As expected, this method is responsible for generating a new question as well as assigning the correct answer to one of the four buttons.

```javascript
generateQuestion() {
	const firstNumber = this.generateRandomNumber(1, 100);
	const secondNumber = this.generateRandomNumber(1, 100);
	const modeNumber = this.generateRandomNumber(1, 2);

	let correctAnswer = 0;

	switch (modeNumber) {
		case MODE_ADDITION:
			correctAnswer = firstNumber + secondNumber;
			this.question = `What's ${firstNumber} + ${secondNumber}?`;
			break;
		case MODE_SUBTRACTION:
			correctAnswer = firstNumber - secondNumber;
			this.question = `What's ${firstNumber} - ${secondNumber}?`;
			break;
		default:
			correctAnswer = 0;
			// this.question = 'Oops, an error occurred :/';
	}
	this.btnData[0].answer = this.generateRandomNumber(correctAnswer - 10, correctAnswer + 10, correctAnswer);
	this.btnData[0].correct = false;
	this.btnData[1].answer = this.generateRandomNumber(correctAnswer - 10, correctAnswer + 10, correctAnswer);
	this.btnData[1].correct = false;
	this.btnData[2].answer = this.generateRandomNumber(correctAnswer - 10, correctAnswer + 10, correctAnswer);
	this.btnData[2].correct = false;
	this.btnData[3].answer = this.generateRandomNumber(correctAnswer - 10, correctAnswer + 10, correctAnswer);
	this.btnData[3].correct = false;

	const correctButton = this.generateRandomNumber(0, 3);
	this.btnData[correctButton].correct = true;
	this.btnData[correctButton].answer = correctAnswer;
}
```

Quite a long function, but don't worry, we'll go through this together.

First of all, we have 3 variables. There is a `firstNumber` and `secondNumber` variable which will have a random number between *1 and 100*. The third variable `modeNumber` will represent the mode of operation. There is also a mysterious `generateRandomNumber()` function. What does that do? It generates a random number for us, but with a slightly different logic. We'll look at it after this function.

Then we have another variable which is probably the most important one - `correctAnswer`. This will contain the correct answer to our question. Make sure to use **let** and not *const* since we need to reassign it.

After declaring our variables we have a `switch case`. It will check the `modeNumber` that we randomly chose between 1 and 2. The reason for that is our very first variable declarations where we assigned a number to our operation modes. This will come in handy now.

We can easily change our logic depending on the randomly chosen operation. If the random number was 1, we would add the `firstNumber` and `secondNumber` variables and put it as the correct answer. If it was 2, we would subtract them. We then assign the appropriate string to `question` data property.

Our question is ready and we also have the correct answer to it. Next, we assign it randomly to a button. The next part might seem confusing but it really isn't.

Each button will have it's *correct* property set to false. A random number will be assigned to the *answer* property. But we can't just assign a **completely** random number. For example, if the question was *What's 2 + 3?* we can't have an option that says *573*. That would obviously be the wrong answer. So our options need to be random but still within a range. We make use of the `generateRandomNumber()` function and pass a minimum number that is 10 *less* than the correct answer and a maximum number that is 10 *more* than the correct answer. 

Sounds like a good solution doesn't it. But there's a third argument passed to the function, what does that do?

I'm glad you asked. Now we don't want our random option generated to be the actual answer, do we? So the third argument tells the function to generate a random number, within the range we passed, but it shouldn't be the actual answer. Thus, all buttons have the wrong answer.

Now what we do is generate a random index position. Then, we assign the correct answer to the button at this index and set it's *correct* property to `true`.

In a nutshell, we have given all the buttons random options and declared they were wrong. Then chose a random button and gave it the correct answer and declared it to be the correct one.

Why did we do it like this though? Couldn't we have chosen a random button, assigned it the answer, and *then* start assigning wrong answers to the remaining buttons? Sure we could have. 

But then, assigning wrong answers to all buttons *except* the correct one which is randomly selected?! It's a pain. Even though it is possible to somehow do it, I'm lazy ;).

For the remaining functions:

```javascript
generateRandomNumber(min, max, except) {
	const rndNumber = Math.round(Math.random() * (max - min)) + min;
	if (rndNumber == except) {
		return this.generateRandomNumber(min, max, except);
	}
	return rndNumber;
},
onAnswer(isCorrect) {
	this.$emit('answered', isCorrect);
}
```

As I explained about the `generateRandomNumber()` function, it takes 3 arguments. The first two are the range within which a random number is to be generated. The third argument is only used when we are making sure the random number generated is not the correct answer. If it matches the `correctAnswer`, the function will run recursively until we get a different number.

The `onAnswer` click handler emits an `answered` event and passes to the parent (`App.vue`) component whether the answer was correct or not.

---

With this, our app is ready. Start a development server to see your code in action.

```bash
#for 3.x
npm run serve

#for 2.x
npm run dev
```

Here is the [GitHub repo](https://github.com/NAPOLEON039/Quiz-App) of the code for reference. I hope you had fun making it. I would love to see you modify and improve upon it. Look forward to hearing from you in the comment section below.

---

This app is originally from the [Vue course](https://www.udemy.com/vuejs-2-the-complete-guide) on Udemy by [Maximilian Schwarzmüller](https://twitter.com/maxedapps). He teaches lots of amazing things about Vue and you can surely find something of value in that course. So definitely check it out.

> *I'm currently taking his course and find it valuable. This is not a sponsored post but a personal recommendation.*