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

DefineTemplate / ReuseTemplate - Slotted content is incorrectly used between instances #3881

Open
7 tasks done
matthew-dean opened this issue Mar 22, 2024 · 2 comments
Open
7 tasks done

Comments

@matthew-dean
Copy link

matthew-dean commented Mar 22, 2024

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

@matthew-dean
Copy link
Author

matthew-dean commented Mar 22, 2024

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.

@matthew-dean
Copy link
Author

matthew-dean commented Mar 22, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant