Search code examples
javascriptvue.jsvuejs2gsap

Reset a Vue counter component from the parent component


I have a Vue counter which counts up from 1 to 10. It's implemented as a component. I need to reset the counter from the main app. What's the best way to do this? As you can see from my example, the watch method doesn't do the job:

Vue.component('my-counter', {
  template: "#counter-vue",
  data: function() {
    return {
      age_now: null
    }
  },
  props: {
    age: Number
  },
  mounted: function() {
    this.age_now = this.age;
    TweenLite.to(this, 10, { age_now: this.$root.age_end, roundProps: "age_now", ease:Linear.easeNone });
  },
  watch: {
    age(val) {
      this.age_now = val;
    }
  }
});

var app = new Vue({
  el: '.container',
  data: {
    age: 1,
    age_end: 10
  },
  methods: {
    reset() {
      this.age = 1;
    }	
  }
});
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>

<script type="text/x-template" id="counter-vue">
	<div>
		<p>Age: {{age_now}}</p>
	</div>
</script>
			
<div class="container">
	  <h1>Vue Component Reset</h1>
		<my-counter :age="age"></my-counter>
		<button @click.prevent="reset">Reset</button>
    <p>What's the best way to implement the reset of the component to restart the count up from 1 to 10?</p>
</div>

See also on CodePen: https://codepen.io/MSCAU/pen/OagMJy


Solution

  • First of all, your watcher for age in the my-counter component isn't ever firing because you aren't ever changing the value of the age prop. If it's initialized to 1 and gets set to 1 in the click-handler, it won't trigger the watcher because the value didn't change.

    But second of all, it'd make more sense, in this case, to just move the reset method to the my-counter component and then call it from the parent scope via a ref, like so:

    <my-counter ref="counter" :age="age"></my-counter>
    <button @click.prevent="$refs.counter.reset">Reset</button>
    

    It looks like you'll also need to call the TweenLite.to method again if you want the counter to increment again. So it'd be good to pull that logic into its own method (say count) that you can call from the mounted hook and from the reset method.

    Also, I noticed that it also seems like the TweenLite.to method is overriding the binding for the age prop until the counter has finished incrementing. If you want to reset the counter before the TweenLite.to method has finished, you'll need to store a reference to the returned tween object and then call its kill method before the counter starts.

    Finally, I'm seeing that you're referencing the this.$root.age_end in the object passed to the TweenLite.to. Except in rare cases, this is considered bad practice, as now the component unnecessarily depends on the root Vue instance always having an age_end property and it obscures the flow of data. Since that value appears to be static, you should just set it as a data property of the component. Or at least pass it in as a prop from the parent.

    Here's a working example with my suggested changes:

    Vue.component('my-counter', {
      template: "#counter-vue",
      data: function() {
        return {
          age_now: null,
          age_end: 10,
          tween: null,
        }
      },
      props: {
        age: Number
      },
      mounted: function() {
        this.age_now = this.age;
        this.count();
      },
      methods: {
        count() {
          if (this.tween) {
            this.tween.kill();
          }
        
          this.tween = TweenLite.to(this, 10, { 
            age_now: this.age_end, 
            roundProps: "age_now", 
            ease: Linear.easeNone 
          });
        },
        reset() {
          this.age_now = 1;
          this.count();
        }
      }
    });
    
    var app = new Vue({
      el: '.container',
      data: {
        age: 1,
      }
    });
    <script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    
    <script type="text/x-template" id="counter-vue">
      <div>
        <p>Age: {{age_now}}</p>
      </div>
    </script>
    			
    <div class="container">
      <h1>Vue Component Reset</h1>
      <my-counter ref="counter" :age="age"></my-counter>
      <button @click.prevent="$refs.counter.reset">Reset</button>
    </div>