Search code examples
vue.jsvuejs3slotvue-render-function

How to get a change of prop of useSlot() in Vue 3


I have a question about how to get the change of props retrieved from useSlots()

this is a file declaring Parent component with Child component as Parent component's slot. Also I passed some props to Child component.

<script setup>
import { ref } from "vue";
import Parent from "./components/Parent.vue";
import Child from "./components/Child.vue";

const name = ref("Nagisa");
function updateName() {
  name.value = "Daniel";
}
</script>

<template>
  <Parent><Child :name="name"></Child></Parent>
  <hr />
  <button @click="updateName">update name</button>
  <p>name: {{ name }}</p>
</template>

In Parent component, because of some limitations, I need to call render function and re-store what has been declared in slot.

// Parent component

<script setup>
import Child from "./Child.vue";
import { h, useSlots } from "vue";

const slots = useSlots();
const defaultSlot = slots.default(); // <====== retrieving slot of Parent component

const render = (e) => {
  return h("div", {}, h(Child, defaultSlot[0].props)); <======= restoring Child with props coming from defaultSlot of Parent
};

// ========================
const render = (e) => {
  return h("div", {}, defaultSlot); <==== or alternatively this renders Child correctly
};
// ========================
</script>
<template>
  <render></render>
</template>

// Child component


<script setup>
defineProps(["name"]);
</script>
<template>
  <div>
    {{ name }}
  </div>
</template>

This correctly renders Child component with Parent component but Child component never updates if its prop value changes

enter image description here

After I click updateName button

enter image description here

Is there any way to catch and update the change of Child component's prop in Parent component from useSlot()?


Solution

  • Your usage of slot in Parent is incorrect. Whole point of slots is to render something the Parent knows nothing about (ie. anything you pass to it). So rendering Child directly is wrong (as it tights Parent into always rendering Child instead of slot content)

    Second problem is using const defaultSlot = slots.default(); inside the setup - slots are functions and arguments of those functions (such as props and other reactive data from the parent scope) change when the template is re-rendered. So the slot function needs to be executed every time the Parent re-renders. But setup runs only once. This is why you don't see any update of Child. Instead use a slot directly in render function:

    // Parent component
    <script setup>
    import { h, useSlots } from "vue";
    
    const slots = useSlots();
    
    const render = () => {
      return h("div", {}, slots.default ? slots.default() : null);
    };
    </script>