1

(Vue 3, options API)

The problem: Components rerender when they shouldn't.

The situation:

  1. Components are called with a prop whose value comes from a method.
  2. The method cannot be replaced with a computed property because we must make operations on the specific item (in a v-for) that will send the value processed for that component.
  3. The method returns an Array. If it returned a primitive such as a String, components wouldn't rerender.

To reproduce: change any parent's data property unrelated to the components (such as showMenu in the example below).

Parent

<template>
  <div>
    <div id="menu">
      <div @click="showMenu = !showMenu">Click Me</div>
      <div v-if="showMenu">
        Open Console: A change in a property shouldn't rerender child components if they are not within the props. But it does because we call myMethod(chart) within the v-for, and that method returns an array/object. 
      </div>
    </div>
    <div v-for="(chart, index) in items" :key="index">
      <MyComponent :table="myMethod(chart)" :title="chart.title" />
    </div>
  </div>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
  components: {
    MyComponent,
  },
  data: function () {
    return {
      showMenu: false,
      items: [{ value: 1 }, { value: 2 }],
    };
  },
  methods: {
    myMethod(item) {
      // Remove [brackets] and it doesn't rerender all children
      return ['processed' + item.value];
    }
  }
};
</script>

Child

<template>
  <div class="myComponent">
    {{ table }}
  </div>
</template>
<script>
export default {
  props: ['table'],
  beforeUpdate() {
    console.log('I have been rerendered');
  },
};
</script>
<style>
.myComponent {
  width: 10em;
  height: 4em;
  border: solid 2px darkblue;
}
</style>

Here's a Stackblitz that reproduces it https://stackblitz.com/edit/r3gg3v-ocvbkh?file=src/MyComponent.vue

I need components not to rerender. And I don't see why they do.

Thank you!

daniel p
  • 741
  • 7
  • 12

1 Answers1

1

To avoid this unnecessary rerendering which is the default behavior try to use v-memo directive to rerender the child component unless the items property changes :

<div v-for="(chart, index) in items" :key="index" v-memo="[items]">
   <MyComponent :table="myMethod(chart)" :title="chart.title" />
 </div>
Boussadjra Brahim
  • 82,684
  • 19
  • 144
  • 164
  • Thanks, Boussadjira. `v-memo` prevents rerender even when there are actual changes that justify the rerender. The problem seems to be that Vue’s reactivity only does shallow comparisons on props, so it assumes changes even if there weren't. – daniel p Sep 09 '22 at 05:13
  • Also, I'd rather avoid stringifying if there's any other option. working example https://stackblitz.com/edit/r3gg3v-ls8e4a?file=src/MainContainer.vue – daniel p Sep 09 '22 at 05:30
  • You're welcome, I understand that you want the app works in this example but without stringifying? – Boussadjra Brahim Sep 09 '22 at 06:21
  • Exactly. My components receive deep props such as arrays of objects. Vue rerenders all components because it performs a shallow comparison, assuming any unrelated data change may affect the props. Stringify solves it by flattening, but it's inelegant at the least. – daniel p Sep 09 '22 at 10:09
  • 1
    @danielp, I'll try to find a solution since this is interesting, if you get it let me know – Boussadjra Brahim Sep 10 '22 at 05:44