hyperbotauthor
hyperbotauthor

Reputation: 417

"Non-function value encountered for default slot." in Vue 3 Composition API component

MCVE

https://github.com/hyperbotauthor/minvue3cliapp

MCVE live

https://codesandbox.io/s/white-browser-fl7ji

I have a Vue 3 cli-service app, that uses composition API components with slots.

The HelloWorld component renders the slots it receives in a div:

// src/components/Helloworld.js
import { defineComponent, h } from "vue";

export default defineComponent({
  setup(props, { slots }) {
    return () => h("div", {}, slots);
  }
});

The Composite component uses HelloWorld in its setup function and fills in its slots:

// src/components/Composite.js
import { defineComponent, h } from "vue";

import HelloWorld from "./HelloWorld";

export default defineComponent({
  setup(props, { slots }) {
    return () =>
      h(HelloWorld, {}, [h("div", {}, ["Div 1"]), h("div", {}, ["Div 2"])]);
  }
});

The app uses both ways to render the same two divs:

<template>
  <!--<img alt="Vue logo" src="./assets/logo.png">-->
  Works with plain slots
  <HelloWorld>
    <div>Div 1</div>
    <div>Div 2</div>
  </HelloWorld>
  Triggers warning when slots are used from other component
  <Composite> </Composite>
</template>

<script>
import HelloWorld from "./components/HelloWorld";
import Composite from "./components/Composite";

export default {
  name: "App",
  components: {
    HelloWorld,
    Composite,
  },
};
</script>

<style>
</style>

The Composite component triggers this warning:

Non-function value encountered for default slot. Prefer function slots for better performance. 

The same warning is not triggered when I use HelloWorld from only the template.

I don't understand what is the difference if I use the slots from a template or from an other component.

What is the point of this warning?

Is there any way I can remove this warning?

Upvotes: 22

Views: 20700

Answers (5)

truefusion
truefusion

Reputation: 493

A non-function value for default slot warning appears to occur when no default slot was available for the custom component. (For example, if you use resolveComponent for the first argument of h.) In such a case, I assume Vue tries to compensate for the lack of a defined default slot, which itself is technically unnecessary and redundant if the component were completely available. This overhead would be what reduces performance.

Upvotes: 0

DJafari
DJafari

Reputation: 13545

correct solution to hide this warning is :

change array children to :

{
  default: () => children,
}

for your example it must be change to this :

return () =>
  h(HelloWorld, {}, {
    default: () => [h("div", {}, ["Div 1"]), h("div", {}, ["Div 2"])],
  });

Upvotes: 4

Rim
Rim

Reputation: 269

I want to add another fix to the thread if anyone ever encounters something else but similar to this You may want to use

h(YourComponent, () => "some label")

Or

h(YourComponent, {}, "some label")

Instead of this

h(YourComponent, "some label") // non-function value enco...

This is likely a conditional bug of the render function h(), looks silly but it took me an hour to solve the problem

Upvotes: 1

Doug
Doug

Reputation: 35136

You can also easily get this error when you pass an empty array to the children argument when constructing nested components.

For example:

const children = () => {
  const marker = h(ATreeDefaultMarker, {
    hasChildren: item.children.length > 0,
    isOpen: item.open ?? false,
  }, []); // <--------------------- This

  const indent = h(ATreeDefaultIndent, {
    item,
  }, []); // <--------------------- This

  const display = h(ATreeDefaultItem, { item, events });
  return [indent, marker, display];
};

return () => h(ATreeDefaultLine, children)];

Although the top level call to h is returning an array of VNode to put into the default slot, the children are being accidentally rendered with an empty array for the child list:

h(..., props, []) <--- like this.

Vue can't distinguish between an empty child array and one that actually has children, so it raises an error like:

Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance. 
 at <ATreeDefaultIndent> 
 at <ATreeDefaultLine> 

The warning is simply that the child array is not a function, and in this case, it makes no difference.

...however, the linter can't tell that.

tldr

If you see this error, it's also worth checking you're not accidentally passing an empty child list like [] to h accidentally.

Upvotes: 2

tony19
tony19

Reputation: 138276

The warning is about the array of VNodes created in the setup()'s render function in Composite.js.

// src/components/Composite.js
export default defineComponent({
  setup(props, { slots }) {
    return () =>
      h(HelloWorld, {}, [h("div", {}, ["Div 1"]), h("div", {}, ["Div 2"])]);
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  }
});

This is inefficient because the child slot is rendered before the HelloWorld component could even use it. The child slot is essentially rendered in the parent, and then passed to the child. Wrapping the child slot generation in a function defers the work until the child is rendered.

I don't understand what is the difference if I use the slots from a template or from an other component.

@vue/compiler-sfc compiles the <template> from SFCs into a render function, where slots are passed as functions, which avoids the warning you observed.

Solution

Instead of rendering the child slot in the parent (i.e., passing an array of VNodes as the slots argument directly), wrap it in a function:

// src/components/Composite.js
export default defineComponent({
  setup(props, { slots }) {
    return () =>          👇
      h(HelloWorld, {}, () => [h("div", {}, ["Div 1"]), h("div", {}, ["Div 2"])]);
  }
});

Note the inner h() calls don't need this function wrapper because they're all rendered together with the default slot by the child.

demo

Upvotes: 47

Related Questions