Vue 3 Render Functions - Append child element to VNode on click event

1.4k Views Asked by At

I’m trying to dynamically append a VNode to a container on a click event from the user.

I was expecting the code below to append a new child to the container div each time the button is clicked but what’s happening is that upon each click the previous child is replaced by the new one inside the div as the following gif illustrates.

How could I following the same concept, of dynamically creating VNodes, achieve the outcome of appending a node without replacing the previous one?

import {defineComponent, h, createVNode, render} from "vue";

export default defineComponent({
    setup() {
        return () => {
          const container = h('div', []);
          const button = h('button', {
            onClick: () => {
              const node = createVNode('div', null,[`Time: ${new Date().toISOString()}`]);
              node.appContext = container.appContext;
              render(node, container.el as any);
            }
          }, ['Add New'])
          container.children?.push(button);
          return container;
        };
    }
})

(For reference I’ve followed this tutorial)

Thank you for your time

1

There are 1 best solutions below

0
Tolbxela On

With render(node, container.el as any); you replace the inner HTML of your div container every time with the new created node. This approach is questioned. Since:

This approach relies on internal methods (createVNode and render), which could be refactored or removed in a future release.

I would just use Vue reactivity instead of rendering new vnodes.

const { createApp, defineComponent, h, createVNode, render, ref } = Vue;

const MyComponent = {
 setup() {
        const children = ref([]);        
        return () => {
          const button = h('button', {
            onClick: () => {
              children.value.push(`Time: ${new Date().toISOString()}`);
            }
          }, ['Add New']);
          const list = h('ul', 
            children.value.map((text) => {
               return h('li', null, text)
            })
          );
          return h('div', [button, list]);
        };
    }
}

const App = {}
const app = createApp(App)
app.component('MyComponent', MyComponent)
app.mount('#app')
<div id="app"> 
  <my-component />
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

Or even do it much simpler, like this

const { createApp, ref } = Vue;

const MyComponent = {
 setup() {
    const items = ref([]);
    const addNew = () => items.value.push(`Time: ${new Date().toISOString()}`);
    return {items, addNew}
 },
  template: `
  <div>
      <button @click="addNew()">Add New</button>
      <ul><li v-for="text in items">{{ text }}</li></ul>
  </div>`
  }

const App = {
  components: {MyComponent}
}
const app = createApp(App)
app.mount('#app')
<div id="app"> 
  <my-component />
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>