Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

beforeUpdate/updated not triggered on component when slot content in child component changes #4686

Closed
LogicAndTrick opened this issue Sep 27, 2021 · 5 comments

Comments

@LogicAndTrick
Copy link

Version

3.2.19

Reproduction link

sfc.vuejs.org/

Steps to reproduce

  1. Note that NoSlot.vue and WithSlot.vue are identical aside from the use of a slot component in WithSlot.vue
  2. Click the "Add value" button
  3. Observe the output of both components

What is expected?

Both the no-slot and with-slot components should output the same result. The onBeforeUpdate and onUpdated triggers fire on both components.

What is actually happening?

The no-slot component behaves as expected, but the with-slot component is never firing its onBeforeUpdate hook, causing the ref array to never get emptied.


I am migrating a code base from Vue 2. The application makes heavy use of array refs and slots, and I would have expected the solution provided in the migration guide to give me the same behaviour as Vue 2. However, beforeUpdate is not called on the parent component when the slot content changes, even though the template belongs to the parent component and it is rendered in the scope of the parent component. This means the the array ref is never being emptied and continues to grow with each update.

If this is expected behaviour, then a method to get an array ref when the child elements are inside a slotted component should be added to the documentation or migration guide.

@yyx990803
Copy link
Member

This is expected behavior in Vue 3 since the parent component in this case did not update, which is a form of optimization.
A workaround here is using the @vnode-updated="updateRefs" event on a container element inside the slot instead.

@LogicAndTrick
Copy link
Author

Thank you, the workaround is suitable for my use case. I understand the need for the optimisation, but the behaviour seems unintuitive to me - it looks like the component is being updated (ref callbacks are being called in the component's scope), and yet the update hooks aren't called on that component. In my mind the component is being updated, the fact that the content is in a slot (and therefore inside a child component) is something I shouldn't need to worry about.

I don't think it's too uncommon to have refs within slotted content. Here's a basic example using Vuetify which has a number of container components such as <v-row> and <v-col>. Having to worry about the scope of the slot and vnodes makes it a lot harder to use components and refs. Is there some solution that can be added to the framework to make managing this pattern easier? At the very least I think this should be noted in the docs and the migration guide as something which could catch people out. I'm happy for this issue to be closed if you don't agree.

@LinusBorg
Copy link
Member

The slot behavior that you find unintuitive exists in order to improve on one of the more common perf problems in Vue 2 applications: slots updates forcing a chain of ancestor components to re-render even though only the slot content is affected.

Bu running the slot rendering in the child component, instead of the parent, we can collect reactive dependencies in the slot as dependencies of the child's template instead of the parent's, meaning the child can re-render independently when reactive data (only) in the slot changed.

We will definitely keep this behavior (which already existed in the form of "scoped slots" in Vue 2).

We can consider how to make refs on v-for more ergonomic, and yes, at least document the vnode hook pattern in the docs.

@yyx990803 wouldn't a vnode hook on the component itself also work, or are there edge cases that I#m missing?

<template>
  <slot-comp @vnode-before-update="refs.length = 0">
    <h3>{{name}}</h3>
  	<div v-for="v in store.vals" :ref="(r) => { if (r) refs.push(r); }">Component #{{v}}</div>
  </slot-comp>
</template>

(Playground)

@msimpson
Copy link

msimpson commented Oct 20, 2021

@LinusBorg

I'd also document the need to hoist beforeUpdate or employ some generic slot wrapper in nested situations.

For example, the following will not work as the @vnode-before-update listener in app only reaches down one level.

slot-a

<template>
  <slot/>
</template>

slot-b

<template>
  <slot-a>
    <slot/>
  </slot-a>
</template>

app

<template>
  <slot-b @vnode-before-update="clearItemRefs">
    <span v-for="item in list" :ref="setItemRef">{{ item }}</span>
  </slot-b>
</template>

You'd need to hoist beforeUpdate, like:

slot-a

<script>
  export default {
    emits: [ 'beforeUpdate' ],
    beforeUpdate() {
      this.$emit( 'beforeUpdate' );
    };
  };
</script>

<template>
  <slot/>
</template>

app

<template>
  <slot-b @beforeUpdate="clearItemRefs">
    <span v-for="item in list" :ref="setItemRef">{{ item }}</span>
  </slot-b>
</template>

Or, construct a generic wrapper to capture updates:

slot-events

<script>
  export default {
    emits: [ 'beforeUpdate' ],
    beforeUpdate() {
      this.$emit( 'beforeUpdate' );
    };
  };
</script>

<template>
  <slot/>
</template>

app

<template>
  <slot-b>
    <slot-events @beforeUpdate="clearItemRefs">
      <span v-for="item in list" :ref="setItemRef">{{ item }}</span>
    </slot-events>
  </slot-b>
</template>

As I build slotted components derived from others, I'm using the latter pattern to maintain composability.

I don't want to preemptively guess where I need to hoist.

Or, rely on the happenstance of an immediate parent where @vnode-before-update would suffice.

Although, this complicates styling.

:slotted becomes ineffective and I'm forced to use :deep with a child combinator to mimic the behavior.

@LogicAndTrick
Copy link
Author

It looks like support for array refs was added back in with commit 41c18ef, which fixes the difficulties with array refs as it's no longer necessary to use function refs for them. Closing this issue as the slot updated triggering is intended behaviour and there is now a good solution for refs in slotted content.

@github-actions github-actions bot locked and limited conversation to collaborators Oct 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants