Andrew C.
Andrew C.

Reputation: 461

Build a dynamic number of Vue slots within a Render function

I'm trying to build a custom component from a render function.

This component being rendered accepts any number of slots. In the example below there are three available slots (named element_1, element_2, element_3).

The below Array.reduce() is meant to be equivalent to:

scopedSlots: {
  "element_1": () => createElement('div', 'hello world'),
  "element_2": () => createElement('div', 'hello world'),
  "element_3": () => createElement('div', 'hello world'),
}

This is a slimmed down example with Array.reduce():

const records = [
  {
    "index": 1,
  },
  {
    "index": 2,
  },
  {
    "index": 3,
  }
]

render: function (createElement) {
  return createElement("replicator-component", {
    attrs: { elements: records.length},

    scopedSlots: records.reduce((a,x) => ({...a, 
      ['element_' + x.index]: 
      () => { createElement( 'div', 'hello world') }}), {})
  });
},

However nothing renders and there's no errors to guide me. Any ideas?

Upvotes: 1

Views: 2347

Answers (3)

Boussadjra Brahim
Boussadjra Brahim

Reputation: 1

That reduce method doesn't work because it's missing a return before createElement('div', 'hello world'):

Full example

const ReplicatorComponent = {

  template: `
 <div>
    <h1>replicator-component</h1>
    
    <slot name='element_1'></slot>
    <slot name='element_2'></slot>
    <slot name='element_3'></slot>
 </div>
`
}

const records = [{
    "index": 1,
  },
  {
    "index": 2,
  },
  {
    "index": 3,
  }
]



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

    let slotContent = records.reduce((a, x) => ({ ...a,
      ['element_' + x.index]:
        () => {
        return  createElement('div', 'hello world')
        }
    }), {})
    return createElement(ReplicatorComponent, {
      attrs: {
        elements: records.length
      },
      scopedSlots: slotContent
    });
  },
})

var app = new Vue({
  el: '#app',

  data: () => ({})
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>

<div id="app">
  test

  <my-component></my-component>
</div>

Upvotes: 0

Bergi
Bergi

Reputation: 665380

The difference is that in your reduce, you are creating the functions as

() => { createElement( 'div', 'hello world') }

while in your hardcoded version (and also in the forEach loop in @Boussadjra's anwer) they are created as

() => createElement('div', 'hello world')

which actually return the created element. It's nothing to do with the use of reduce, which is fine.

const ReplicatorComponent = {
  template: `<div>
    <h1>replicator-component</h1>
    <slot name='element_1'></slot>
    <slot name='element_2'></slot>
    <slot name='element_3'></slot>
  </div>`
};

const records = [
  { "index": 1 },
  { "index": 2 },
  { "index": 3 },
];

Vue.component('my-component', {
  render: function(createElement) {
    return createElement(ReplicatorComponent, {
      attrs: {
        elements: records.length
      },
      scopedSlots: records.reduce((a,x) => ({
        ...a, 
        ['element_' + x.index]: () => 
          createElement( 'div', 'hello world')
       }), {})
    });
  },
});

new Vue({
  el: '#app',
  data: () => ({})
});
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>

<div id="app">
  <my-component></my-component>
</div>

Upvotes: 2

user14728407
user14728407

Reputation: 111

i think scoped slots should be functions that take props as their parameter according to the Vue.js docs

export default {
  render(createElement) {
 
    // ...
    // some other stuff
    // ...

    // Scoped slots in the form of
    // { name: props => VNode | Array<VNode> }
    scopedSlots: {
      default: props => createElement('span', props.text)
    },
  }
}

so maybe you should try that

also you can accomplish the same thing using vue's new unified v-slot system

<!-- page component -->
<template>
  <some-component>
    <template v-for="slot in scopedSlots" v-slot:[slot]="props">
      hello {{props}}
    </template>
  </some-component>
</template>

<!-- some-component.vue -->

<template>
  <div>
    <slot v-for="slot in Object.keys($slots)" :name="slot"></slot>
  </div>
</template>

Upvotes: 0

Related Questions