Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue Child Component Doesn't Update on Prop Change

I'm updating an 'exercise' prop that gets sent to a 'workout' component in Vue. In the child component, I'm emitting a function to increment the set you're on. The function is firing in the parent component (I'm getting console.logs()), but the child component isn't re-rendering.

The Parent:

<ExerciseListItem
  v-for="(exercise) in exercises"
  v-on:completeSet="completeSet"
  v-on:selectExercise="selectExercise"
  v-bind:exercise.sync="exercise"
  v-bind:isActiveExercise="exercise.slug === activeExerciseSlug"
  v-bind:key="exercise.slug"
/>

The methods:

methods: {
  completeSet: function(slug) {
    console.log("complete-set", slug);
    const exercise = this.getExerciseBySlug(slug);
    exercise.completedSets++;
    if (exercise.completedSets === exercise.totalSets) {
      this.completeExercise(slug);
    }
  },
  completeExercise: function(slug) {
    this.getExerciseBySlug(slug).isComplete = true;
    console.log("COMPLETE-exercise", slug);
  },
  getExerciseBySlug: function(slug) {
    return this.exercises.find(exercise => exercise.slug === slug);
  },
  selectExercise: function(selectedSlug) {
    this.activeExerciseSlug = selectedSlug;
  }
},

The Child Template

<li
  v-bind:class="{ 'white-box': true, complete: exercise.isComplete }"
  v-on:click="exercise.totalSets > 1 ? $emit('selectExercise', exercise.slug) : $emit('completeSet', exercise.slug)"
>

Here's the project on Github

And a live demo

Help appreciated 😊

like image 547
Ross Whitehouse Avatar asked Sep 18 '19 20:09

Ross Whitehouse


2 Answers

The component is not updating because the nested values of your prop are not reactive, hence, vue does not notice they are changing.

How to make nested properties reactive in Vue

The following applies to all state in your vue app - no matter whether it is located in a vuex store, your data() option, or inside a prop.

By default, vue only makes state reactive if it is declared. That means dynamically (at runtime) assigned state is not reactive (e.g. mounted() { this.bar = 'two' } will not create a reactive property bar in your data()).

If you want to dynamically create reactive state there are two rules you need to follow:

  1. All properties need to be set with Vue.set(rootObject, key, value) (e.g. mounted() { this.$set(this, 'bar', 'two') } - this.$set() is an alias for Vue.set()). Read about it here: Vue Doku

  2. Arrays need to be filled with native array properties (Array.prototype.push(), etc.). Setting array elements by index will not make these elements reactive. Note: Vue.set(rootArray, 1, value) also will not work - as it sets the element by index. Read more about it here: Vue Gotchas

like image 90
MarcRo Avatar answered Nov 10 '22 14:11

MarcRo


If needed you can call this.$forceUpdate() in the parent component after the emit, which will force a re-render.

Not sure the underlying problem on why your child component is not triggering the update automatically.

like image 45
Scornwell Avatar answered Nov 10 '22 13:11

Scornwell