Search code examples
javascriptvue.jssettimeoutarrow-functions

Why I've gotten "Uncaught TypeError: Cannot read property 'counter' of undefined" despite I'm using arrow function for SetTimeout?


The following small count down application cause Uncaught TypeError: Cannot read property 'counter' of undefined at the timing of evaluation of this.counter.

<template>
  <v-app>
    <v-content>
      Redirect after {{counter}} sec.
    </v-content>
  </v-app>
</template>

<script>
/* eslint-disable no-console */
export default {
  name: 'App',

  components: {
  },
  mounted() {
    this.countdown();
  },
  created() {
  },
  methods: {
    countdown: () => {
      setTimeout(() => {
        if (--this.counter <=0){
          location.href=this.$redirectURL;
        } else {
          this.countdown();
        }
      }, 1*1000);
    }
  },

  data: () => ({
    counter: 5,
  }),
};
</script>

Uncaught TypeError: Cannot read property 'counter' of undefined as follows:

enter image description here

I have no idea why coutner is evaluated as undefined despite I'm using arrow function, which means the scope of "this pointer" must be lexical. Thank you for your suggestions!


Solution

  • The countdown function is an arrow function, which means that the this inside it is inherited from the outer scope. The same is true for the setTimeout callback. So, here:

    export default {
      // ...
      methods: {
        countdown: () => {
          setTimeout(() => {
            if (--this.counter <=0){
              location.href=this.$redirectURL;
            } else {
              this.countdown();
            }
          }, 1*1000);
        }
      },
      // ...
    }
    

    this refers to the this at the top level of the module, which is undefined in your setup.

    You want this to refer to the current instance instead: when calling countdown, it should capture the new this value (of the instance), rather than inheriting the this of the outer scope. So, change:

    countdown: () => {
    

    to

    countdown() {
    

    or to

    countdown: function() {