vueuse / vueuse

Collection of essential Vue Composition Utilities for Vue 2 and 3

Home Page:https://vueuse.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

DefineTemplate / ReuseTemplate - Slotted content is incorrectly used between instances

matthew-dean opened this issue · comments

Describe the bug

If <DefineTemplate> has a slot, this slot will be re-used between component instances.

Screenshot 2024-03-22 at 11 26 24 AM Screenshot 2024-03-22 at 11 26 41 AM

Reproduction

https://stackblitz.com/edit/vitejs-vite-hrgeva?file=src%2FApp.vue,src%2FMyComponent.vue

System Info

System:
    OS: Linux 5.0 undefined
    CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: 1.0 - /bin/jsh
  Binaries:
    Node: 18.18.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.2.3 - /usr/local/bin/npm
    pnpm: 8.15.3 - /usr/local/bin/pnpm
  npmPackages:
    @vueuse/core: ^10.9.0 => 10.9.0 
    vue: ^3.2.25 => 3.2.31

Used Package Manager

npm

Validations

So, I think the long and the short is that documentation is currently wrong (or this is a legit bug). It says:

When using with Options API, you will need to define createReusableTemplate outside of the component setup and pass to the components option in order to use them in the template.

This seems to work only if your template is not bound to the state of the current instance, as it's defined outside of the setup process.

Here is the corrected code (using setup properly, similar to <script setup>) that should be in the example:

<script>
import { defineComponent } from 'vue'
import { createReusableTemplate } from '@vueuse/core'

export default defineComponent({
  setup() {
    const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
    return { DefineTemplate, ReuseTemplate }
  },
})
</script>

<template>
  <component :is="DefineTemplate" v-slot="{ data, msg, anything }">
    <div>{{ data }} passed from usage</div>
  </component>

  <component :is="ReuseTemplate" :data="data" msg="The first usage" />
</template>

That said, maybe there's another way to pass in components that would correctly re-use slots from the parent? 🤷‍♂️ It's possible that these components are just defined incorrectly, and are somehow memo-izing what's being passed into the slot? I feel like other components passed to the Options API handle slotted content correctly, but DefineTemplate is effectively already "re-using" content so I wonder if that re-use logic is just wrong.

Oh yeah! I see the issue!

  1. createReusableTemplate creates a ref on this line.
  2. DefineTemplate sets the render value on this line.
  3. ReuseTemplate does nothing except return the value of render.value on this line.

Therefore, by calling createReusableTemplate outside of the setup function, it creates one instance of render.value across all instances (that are using the Options API).

The way you could make this work according to the example, might be something like the following:

  1. Create a WeakMap in createReusableTemplate that maps instances to render values
  2. In DefineTemplate's setup function, create a new render value and use something like getCurrentInstance to get the component parent and set that in the WeakMap.
  3. In ReuseTemplate, lookup the WeakMap using again the parent instance and return the render value.