0
votes

I am getting the below error when using <slot> multiple times:

Duplicate presence of slot "default" found in the same render tree - this will likely cause render errors.

There are solutions that use "scoped slots" but what I understand is that it's good to use those with v-for. I am not sure, I may be wrong, let me know if I am.

I have a situation where I need to duplicate static content (with markup) in a child component multiple times.

Parent Component:

<template>
  <child-comp>
    <h1>Lorem Ipusm</h1>
    <button @click="fnDoSomething">Yahoo!<button>
    <!-- ... there will be a lot more that will go here in the default slot -->
  <child-comp>     
<template>

Child Component:

<template>
  <div>
    <h2>Need one default slot here</h2>
    <slot><slot>
    <div>
      <h2>Need one more default slot here</h2>
      <slot><slot>
    <div>
  </div>      
<template>

If the above issue can't be fixed or it's a limitation of Vue.js, then please let me know how to clone the slot (or something like that) and have it still be reactive.

1

1 Answers

2
votes

Use render function should be able to implement what you need like below demo:

But you may meet some troubles like this link decribes: Why are duplicated slots bad?.

As the developer from Vue.js core team said:

Vue would re-use the same vnode objects (which represent elements) multiple times during the actual creation of the DOM elements.

The problem with that is that each vnode gets a reference to its corresponding DOM element set.

If you re-use the same vnode objects multiple times, these references get overwritten and you end up with DOM elements that have no representation in the virtual dom, or vnodes that refer to the wrong element.

So In below demo, when you click the button, you will find the first slot of the first case is not synced (VNode is overwritten).

If your default slot is fully static content then doesn't bind to any properties and methods, it should be OK to use multiple default slots in render(). But if not, you have to use scoped slot to implement what you need.

Or you can deep clone this.$slots.default (check VNode constructor), it will avoid the overwritten issue. (check the third case in below demo)

Vue.config.productionTip = false
Vue.component('child', {
  render: function (createElement) {
    return createElement(
      'div',
      [
      this.$slots.default, // default slot
        createElement('div', {
          attrs: {
            name: 'test'
          },
          style: {fontSize: '10px', 'color':'green'}
        }, this.$slots.default) // default slot
      ]
    )
  }
})

function deepClone(vnodes, createElement){
 let clonedProperties = ['text','isComment','componentOptions','elm','context','ns','isStatic','key']
 function cloneVNode(vnode) {
	 let clonedChildren = vnode.children && vnode.children.map(cloneVNode)
	 let cloned = createElement(vnode.tag, vnode.data, clonedChildren)
   clonedProperties.forEach(function(item){
    cloned[item] = vnode[item]
   })
	 return cloned
 }
 return vnodes.map( cloneVNode )
}

Vue.component('child2', {
  render: function (createElement) {
    return createElement(
      'div',
      [
      this.$slots.default, // default slot
        createElement('div', {
          attrs: {
            name: 'test'
          },
          style: {fontSize: '10px', 'color':'green'}
        }, deepClone(this.$slots.default, createElement) ) // default slot
      ]
    )
  }
})

Vue.component('child1', {
  render: function (createElement) {

    return createElement(
      'div',
      [
      this.$slots.default, // default slot
        createElement('div', {
          attrs: {
            name: 'test'
          },
          style: {fontSize: '10px', 'color':'green'}
        }, this.$slots.my) // default slot
      ]
    )
  }
})

new Vue({
  el: '#app',
  data() {
    return {
      test: {
        'item': 'test',
        'prop1': 'a'
      }
    }
  },
  methods:{
    changeData: function() {
      this.test.item='none'
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<button @click="changeData()">Click me!!!</button>
<h1 style="background-color:red">Use multiple default slot:</h1>
<child><h1>{{test}}</h1></child>
<h1 style="background-color:red">Use scoped slot instead:</h1>
<child1><h1>{{test}}</h1><template slot="my"><h1>{{test}}</h1></template></child1>
<h1 style="background-color:red">Use Deep Clone (Default) instead:</h1>
<child2><h1>{{test}}</h1><template slot="my"><h1>{{test}}</h1></template></child2>
</div>