1

I used to use React.js but currently I'm learning Vue.js.

Now I need to dynamically render a part of an element, so I tried to render an jsx like this:

This is a simplified example:

UserListView.vue

<template>
  <UserList :users="users" :columns="columns">
</template>

<script>
import UserList from './UserList';

export default {
  components: {
    UserList,
  },
  data() {
    return {
      users: [ 
        { id: 0, name: 'Alice', age: 18, gender: 'F' },
        { id: 1, name: 'Bob', age: 18, gender: 'M' },
      ],
      columns: [
        { name: 'name', label: 'Name' },
        { name: 'age', label: 'Age' },
        // element with custom render function
        { name: 'gender', label: 'Gender', render: user => (
          <div>{ user.gender === 'M' ? 'Male' : 'Female' }</div>
        ) },
      ]
    }
  }
};
</script>

UserList.vue

<template>
  <table class="UserList">
    <thead>
      <tr>
        <th v-for="column in columns" :key="`column_${column.name}`" :class="column.name">
          {{ column.label }}
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="user in users" :key="`user_${user.id}`">
        <td v-for="column in columns" :key="column.name" :class="column.name">
          {{column.render ? column.render(user) : user[column.name]}}
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script>
export default {
  props: ['users', 'columns'],
};
</script>

But this throws me an error:

enter image description here

So is there a way that I can pass a template into the child and tell it how to render with some arguments? Can it be done with slot? Or is there another way to do it?

6
  • is there a reason you need to do custom render of user's gender like that instead of using v-if in UserList.vue? Commented Jul 1, 2020 at 3:12
  • It's a simplified example. I wish the original component is highly customizable that I don't need to modify the UserList to render each cell what I like. Its render behavior is determined by the parent instead of chaining every possibility in UserList itself. Commented Jul 1, 2020 at 3:15
  • Render element inside child from parent is what <slot> for, but I have no clue how to use <slot> for your particular use case. Commented Jul 1, 2020 at 3:20
  • Hey, maybe this is what you are looking for vuejs.org/v2/guide/render-function.html Commented Jul 1, 2020 at 3:23
  • 1
    did you try <div>${ user.gender === 'M' ? 'Male' : 'Female' }</div>? vue will render it as string. Commented Jul 1, 2020 at 3:27

1 Answer 1

2

One solution that may fit your needs would be to use scoped slots. In this example I just moved the rendering logic to the template but you could render whatever you want in in the slot.

const userList = Vue.component('user-list', {
  template: '#userlist',
  props: ['users', 'columns'],
})

new Vue({
  el: "#app",
  components: {
    userList: userList,
  },
  data() {
    return {
      users: [ 
        { id: 0, name: 'Alice', age: 18, gender: 'F' },
        { id: 1, name: 'Bob', age: 18, gender: 'M' },
      ],
      columns: [
        { name: 'name', label: 'Name' },
        { name: 'age', label: 'Age' },
        // element with custom render function
        { name: 'gender', label: 'Gender'},
      ]
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<div id="app">
  <user-list :users="users" :columns="columns">
    <template  v-slot:gender="{ column, user }">
      {{ user.gender === 'M' ? 'Male': 'Female' }}
    </template>
  </user-list>
</div>

<template id="userlist">
    <table class="UserList">
    <thead>
      <tr>
        <th v-for="column in columns" :key="`column_${column.name}`" :class="column.name">
          {{ column.label }}
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="user in users" :key="`user_${user.id}`">
        <td v-for="column in columns" :key="column.name" :class="column.name">
        <slot :name="column.name" v-bind="{ column, user }">
          {{ user[column.name] }}
        </slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

There's a slot exposed for each column and the user/column exposed through the scoped slot. You can tap into it in the parent context by referencing the slot by name which is currently bound to the column name.

<user-list :users="users" :columns="columns">
    <template  v-slot:name="{ column, user }">
      render whatever in the name column
    </template>
    <template  v-slot:age="{ column, user }">
      render whatever in the age column
    </template>
    <template  v-slot:gender="{ column, user }">
      render whatever in the gender column
    </template>
</user-list>
Sign up to request clarification or add additional context in comments.

1 Comment

I didn’t know you can nest template into an custom element with parameters. This is just what I needed. Thank you so much!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.