4
votes

I have a component which is passed content via a slot. I'm using a render function to output the content. The reason I'm using a render function is because I want to duplicate the content multiple times. When I use this code, everything works fine:

render(createElement){
    return createElement('div', {}, this.$slots.default);
}

When I data that is being passed changes, the output changes as well.

However, since I want to duplicate the slot content, I'm now trying this:

return createElement(
    'div', {},
        [
            createElement('div', { }, this.$slots.default),
            createElement('div', { }, this.$slots.default)
        ]
    )

Now the problem is, when the slot content changes from outside the component, only the content in the second div gets updated, the content in the first div stays the same..

Am I missing something here?

2
I think you will have to manually clone those elements that the default slot refers to. Your second example is behaving how I would expect. First it sets the $slots.default as the child of the element you first create and then immediately moves them again to be the child of the second created element at which point they leave the first. I don't know that Vue has a built in way to recursive clone elements in a format that render() expects.zero298
I suppose that if the elements within $slots.default are outside of the Vue context, you could consider the native cloneNode with deep set to true and see if that works.zero298
I still seems strange to me, as during the first render they both get rendered. It's only when the slot content changes that the issue occurs. I tried cloneNode but that doesn't seem to work (at least this.$slots.default.cloneNode(true) or this.$slots.default[0].cloneNode(true) give me errors saying it's not a function)Leon

2 Answers

3
votes

I can't explain why it happens. But the doc does mention that "VNodes Must Be Unique" in a render function. See https://vuejs.org/v2/guide/render-function.html#Constraints.

Anyway, this is a VNode cloning function, which works, which I discovered from https://jingsam.github.io/2017/03/08/vnode-deep-clone.html.

function deepClone(vnodes, createElement) {
    function cloneVNode(vnode) {
        const clonedChildren = vnode.children && vnode
            .children
            .map(vnode => cloneVNode(vnode));
        const cloned = createElement(vnode.tag, vnode.data, clonedChildren);
        cloned.text = vnode.text;
        cloned.isComment = vnode.isComment;
        cloned.componentOptions = vnode.componentOptions;
        cloned.elm = vnode.elm;
        cloned.context = vnode.context;
        cloned.ns = vnode.ns;
        cloned.isStatic = vnode.isStatic;
        cloned.key = vnode.key;
        return cloned;
    }
    const clonedVNodes = vnodes.map(vnode => cloneVNode(vnode))
    return clonedVNodes;
}

How to use it:

render(createElement) {
    return createElement('div', {}, [
        createElement('div', {}, this.$slots.default),
        createElement('div', {}, [...deepClone(this.$slots.default, createElement)])
    ])
}

Demo: https://jsfiddle.net/jacobgoh101/bz3e0o5m/

1
votes

I found this SO question searching for a way to render the content of a slot multiple times like e.g. for a generic list that can have a template for the content of a list row, which is used for each item.

As of 2020 (in fact earlier) multiple rendering of a slot can be achieved using scoped slots. This is documented here:

https://vuejs.org/v2/guide/components-slots.html#Other-Examples

The documentation says:

Slot props allow us to turn slots into reusable templates that can render different content based on input props

(obviously, if we can use the template to render different content based on props, we can also use it to render the same content)

The example given right there uses a template instead of a render function, but how to use scoped slots in a render function is fortunately also documented:

https://vuejs.org/v2/guide/render-function.html#Slots