skip to Main Content

I am building a quiz app with Vue 3 and Bootstrap 4.

I have this method for checking if the clicked answer is the (same as the) correct answer:

checkAnswer(answer) {
  return answer == this.results[this.questionCount]["correct_answer"];
}

It should be executed upon clicking a list item, as seen below:

<li v-for="answer in answers" @click="checkAnswer(answer)" :class="{'text-white bg-success' : checkAnswer(answer)}">{{answer}}</li>

If the clicked answer is correct, the list item should be added the classes text-white bg-success, otherwise it should be added text-white bg-danger.

const quizApp = {
  data() {
    return {
      questionCount: 0,
      results: [
        {
          question: "The book &quot;The Little Prince&quot; was written by...",
          correct_answer: "Antoine de Saint-Exup&eacute;ry",
          incorrect_answers: [
            "Miguel de Cervantes Saavedra",
            "Jane Austen",
            "F. Scott Fitzgerald"
          ]
        },
        {
          question:
            "Which novel by John Grisham was conceived on a road trip to Florida while thinking about stolen books with his wife?",
          correct_answer: "Camino Island",
          incorrect_answers: ["Rogue Lawyer", "Gray Mountain", "The Litigators"]
        },
        {
          question:
            "In Terry Pratchett's Discworld novel 'Wyrd Sisters', which of these are not one of the three main witches?",
          correct_answer: "Winny Hathersham",
          incorrect_answers: [
            "Granny Weatherwax",
            "Nanny Ogg",
            "Magrat Garlick"
          ]
        }
      ]
    };
  },
  methods: {
    nextQuestion() {
      if (this.questionCount < this.results.length - 1) {
        this.questionCount++;
      }
    },
    prevQuestion() {
      if (this.questionCount >= 1) {
        this.questionCount--;
      }
    },
    checkAnswer(answer) {
      // check if the clicked anwser is equal to the correct answer
      return answer == this.results[this.questionCount]["correct_answer"];
    },
    shuffle(arr) {
      var len = arr.length;
      var d = len;
      var array = [];
      var k, i;
      for (i = 0; i < d; i++) {
        k = Math.floor(Math.random() * len);
        array.push(arr[k]);
        arr.splice(k, 1);
        len = arr.length;
      }
      for (i = 0; i < d; i++) {
        arr[i] = array[i];
      }
      return arr;
    }
  },
  computed: {
    answers() {
      let incorrectAnswers = this.results[this.questionCount][
        "incorrect_answers"
      ];
      let correctAnswer = this.results[this.questionCount]["correct_answer"];
      // return all answers, shuffled
      return this.shuffle(incorrectAnswers.concat(correctAnswer));
    }
  }
};

Vue.createApp(quizApp).mount("#quiz_app");
#quiz_app {
  height: 100vh;
}

.container {
  flex: 1;
}

.quetions .card-header {
  padding-top: 1.25rem;
  padding-bottom: 1.25rem;
}

.quetions .card-footer {
  padding-top: 0.7rem;
  padding-bottom: 0.7rem;
}

.answers li {
  cursor: pointer;
  display: block;
  padding: 7px 15px;
  margin-bottom: 5px;
  border-radius: 6px;
  border: 1px solid rgba(0, 0, 0, 0.1);
  background: #fff;
}

.answers li:last-child {
  margin-bottom: 0;
}

.answers li:hover {
  background: #fafafa;
}

.pager {
  list-style-type: none;
  margin: 0;
  padding: 0;
  display: flex;
  justify-content: space-between;
}

.pager li > a {
  display: inline-block;
  padding: 5px 10px;
  text-align: center;
  width: 100px;
  background-color: #fff;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 999px;
  text-decoration: none !important;
  color: #fff;
}

.pager li > a.disabled {
  pointer-events: none;
  background-color: #9d9d9d !important;
}

.logo {
  width: 30px;
}

.nav-item {
  width: 100%;
}

.card {
  width: 100%;
}

@media (min-width: 768px) {
  .nav-item {
    width: auto;
  }

  .card {
    width: 67%;
  }
}

@media (min-width: 992px) {
  .card {
    width: 50%;
  }
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/vue@next"></script>

<div id="quiz_app" class="container quetions d-flex align-items-center justify-content-center my-3">
    <div v-if="results.length" class="card shadow-sm">
      <div class="card-header bg-light h6">
        {{results[questionCount]['question']}}
      </div>
      <div class="card-body">
        <ul class="answers list-unstyled m-0">

          <li v-for="answer in answers" @click="checkAnswer(answer)" :class="{'text-white bg-success' : checkAnswer(answer)}">{{answer}}</li>
        </ul>
      </div>
      <div class="card-footer bg-white">
        <ul class="pager">
          <li><a href="#" @click="prevQuestion" class="bg-dark" :class="{'disabled' : questionCount == 0}">Previous</a></li>
          <li class="d-flex align-items-center text-secondary font-weight-bold small">Question {{questionCount + 1}} of {{results.length}}</li>
          <li><a href="#" class="bg-dark" :class="{'disabled' : questionCount == results.length - 1}" @click="nextQuestion">Next</a></li>
        </ul>
      </div>
    </div>
  </div>

The problem

To my surprise, the checkAnswer(answer) method is executed before (and in the absence of) any click.

Question

What am I doing wrong?

3

Answers


  1. This is executing it before the click:
    :class="{'text-white bg-success' : checkAnswer(answer)}".
    You’ll need to keep the state in a variable for each answer and update it within the method.
    And as a side node, it is recommended to use :key for looped elements.

    Login or Signup to reply.
  2. The issue is that the checkAnswer is referenced in the html template so it will execute immediately on render. You might want to try instead setting the class to some computed property instead of the function.

    <li v-for="answer in answers" @click="checkAnswer(answer)" :class="{'text-white bg-success' : checkAnswer(answer)}">{{answer}}</li>
    
    Login or Signup to reply.
  3. UPDATED

    checkAnswer() is invoked immediately if used outside a handler.

    maybe this will help, when checkAnswer() is called, store the selected answer selectedAnswer and check if answer is correct isCorrect, and use these 2 states to compare the looped answers.

    <li
      v-for="answer in answers"
      :key="answer"
      @click="checkAnswer(answer)"
      :class="{
        'text-white bg-success' : (selectedAnswer === answer && isCorrect),
        'text-white bg-danger' : (selectedAnswer === answer && !isCorrect)
      }"
    >
      {{answer}}
    </li>
    
    data() {
        return {
            isCorrect: false,
            selectedAnswer: ''
            ...
        }
    },
    methods: {
      checkAnswer(answer) {
        // check if the clicked anwser is equal to the correct answer
        this.selectedAnswer = answer
    
        if (answer == this.results[this.questionCount]["correct_answer"]) {
          this.isCorrect = true
        } else {
          this.isCorrect = false
        }
      },
    }
    
    

    https://jsfiddle.net/renzivan15/fw10q5og/12/

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search