Skip to content

Vue 3 app bug: why is this method executed before any click event occurs?

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/[email protected]"></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?

Answer

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/