2

MCVE

I have a Tabpane component that takes slots as input. When imported from the template it works as expected.

<Tabpane>
    <div caption="I am div 1">Div 1</div>
    <div caption="I am div 2">Div 2</div>
</Tabpane>

However when imported from an other component ( Composite in the example ), then it triggers the following warning:

Slot "default" invoked outside of the render function:
this will not track dependencies used in the slot. Invoke the slot function inside the render function instead. 
// src/components/Composite.js

import { defineComponent, h } from "vue";

import Tabpane from "./Tabpane.vue";

export default defineComponent({
  name: "Composite",
  setup() {
    const slots = [
      h("div", { caption: "I am div 1" }, ["Div 1"]),
      h("div", { caption: "I am div 2" }, ["Div 2"])
    ];
    return () => h(Tabpane, {}, () => slots);
  }
});
hyperbotauthor
  • 307
  • 1
  • 4
  • 13

2 Answers2

5

Solved.

The problem was that I called slots.default() from within setup, but not within the returned render function.

Also this component reflected a very beginner approach to reactivity. By now I know better. The old problematic solution is still there in src/components/Tabpane.vue.

The right solution that triggers no warning is:

// src/components/Tabpane2.vue

<script>
import { defineComponent, h, reactive } from "vue";

export default defineComponent({
  name: "Tabpane2",
  props: {
    width: {
      type: Number,
      default: 400,
    },
    height: {
      type: Number,
      default: 200,
    },
  },
  setup(props, { slots }) {
    const react = reactive({
      selectedTab: 0,
    });
    return () =>
      h("div", { class: ["vertcont"] }, [
        h(
          "div",
          {
            class: ["tabs"],
          },
          slots.default().map((tab, i) =>
            h(
              "div",
              {
                class: {
                  tab: true,
                  selected: i === react.selectedTab,
                },
                onClick: () => {
                  react.selectedTab = i;
                },
              },
              [tab.props.caption]
            )
          )
        ),
        h(
          "div",
          {
            class: ["slotscont"],
            style: {
              width: `${props.width}px`,
              height: `${props.height}px`,
            },
          },
          slots.default().map((slot, i) =>
            h(
              "div",
              {
                class: {
                  slot: true,
                  active: react.selectedTab === i,
                },
              },
              [slot]
            )
          )
        ),
      ]);
  },
});
</script>

<style>
.tab.selected {
  background-color: #efe;
  border: solid 2px #afa !important;
  border-bottom: transparent !important;
}
.tab {
  background-color: #eee;
}
.tabs .tab {
  padding: 5px;
  margin: 2px;
  border: solid 2px #aaa;
  border-radius: 8px;
  border-bottom: transparent;
  cursor: pointer;
  user-select: none;
  transition: all 0.5s;
  color: #007;
}
.tabs {
  display: flex;
  align-items: center;
  margin-left: 5px;
}
.vertcont {
  display: flex;
  flex-direction: column;
  margin: 3px;
}
.slotscont {
  position: relative;
  overflow: scroll;
  padding: 5px;
  border: solid 1px #777;
}
.slot {
  visibility: hidden;
  position: absolute;
}
.slot.active {
  visibility: visible;
}
</style>
hyperbotauthor
  • 307
  • 1
  • 4
  • 13
0

Slots need to be invoked within the render function and or the <template> box to ensure they keep their reactivity.

A full explanation can be found in this post: https://zelig880.com/how-to-fix-slot-invoked-outside-of-the-render-function-in-vue-3

Zelig880
  • 500
  • 4
  • 6