Subscribe on changes!

Component re-renders each time after changing the state if the event is attached to the component

avatar
Feb 15th 2021

Version

3.0.5

Reproduction link

https://github.com/sanjade/vue3-bug

Steps to reproduce

git clone https://github.com/sanjade/vue3-bug
cd vue3-bug && yarn install
yarn dev
Click `Add` button
See in console `<p>Navbar</p> onMounted()` getting rebuilt each time you click `Add` button

What is expected?

So I have a component named ComponentRenderer.js which basically accepts html via a prop and renders it. And in html supplied to this component in App.vue, it has a for loop which uses count from vuex.

The html also includes the same component <component-renderer> which renders <p>Navbar</p> and also has onClick event attached to it. Now, whenever the count is increased via the button, the navbar gets rebuild each time.

This happens only if onClick event is attached to the component. If I remove onClick event then the navbar doesn't gets rebuild each time.

I expect navbar to not get rebuild everytime the count is increased.

What is actually happening?

Navbar keeps on rebuilding everytime the count is increased.

avatar
Feb 15th 2021

Your reproduction doesnt do what it describes. It has a store but doesn't use them for any kind of loop.

Please provide a complete reproduction that actually demonstrates the problem.

avatar
Feb 15th 2021

@LinusBorg If you goto src/App.vue, there's this html in setup function:

 const html = `
<component-renderer :html="'<p>Navbar</p>'" @click="log('Test')"></component-renderer>
<p>BODY</p>
<button  @click="$store.state.count++">Add</button>
<div v-for="a in $store.state.count">{{ a }}</div>
`

As you can see there's a for loop in that html. Now, I supply this html to component-renderer component for Vue to compile html to Vue template. Now, click Add in the webpage and you can see <component-renderer :html="'<p>Navbar</p>'" @click="log('Test')"></component-renderer> component rebuilds everytime the count is increased. See the console log This only happens because of @click="log('Test')" in the html. If I remove this event the the navbar doesn't get rebuilt.

avatar
Feb 15th 2021

Ah, gotcha

avatar
Feb 15th 2021

I think two problems, combined, led to this effect:

@click="log('Test')" generates an inline function: () => log('Test'), which make the inner component update, technically, the passed listener changed. The SFC compiler can cache these kinds of inline function, but the runtime compiler that you are using, can't.

Also, you create the component object in render(), which means whenever the inner component-renderer updates (because of the listener), it will create a new, different component, which will have to be mounted.

Solutions:

  1. create the component once, in setup or created. That will keep the component from being re-created
  2. Avoid the generation of an inline listener function by passing a function without arguments:
@click="logTest"

function logTest () {
  log('Test')
}

The first one is the important fix, the second one an optimization.