Vue3 Render Functions: Passing directives

102 Views Asked by At

I'm trying to create a Confirmation Dialog component that works via a composable (I'm not sure how good/bad of an idea that is).

Thus far, I have the following code, which almost works. You will notice the unmount function is using Javascript which is incorrect because I'm then manipulating the real DOM vs the virtual DOM (and the entire app gets wiped out).

I guess my question is, how can I call this function (confirm), which takes in some properties and then render it and then unmount it cleanly?

import { h, render } from 'vue'

export default function confirmationDialog() {
    const confirm = ({ title, description, callback }) => {
        const parent = document.getElementById('app')

        const child = h(
            'div',
            {
                id: 'confirmation-dialog',
                class: 'custom-popup-container',
            },
            [
                h('div', { class: 'custom-popup' }, [
                    h('div', { class: 'popup-header' }, [
                        h('h2', { class: 'popup-title' }, title),
                        h(
                            'a',
                            {
                                class: 'popup-close',
                                onClick: () => unmount(),
                            },
                            [h('span', { class: 'icon-close' })],
                        ),
                    ]),
                    h('div', { class: 'popup-content' }, [
                        h('p', {}, description),
                    ]),
                    h('div', { class: 'pop-footer' }, [
                        h('div', { class: 'footer-btns' }, [
                            h(
                                'button',
                                {
                                    class: 'btn btn-lg btn-white',
                                    onClick: () => unmount(),
                                },
                                'Cancel',
                            ),
                            h(
                                'button',
                                {
                                    class: 'btn btn-lg btn-primary',
                                    onClick: () => {
                                        callback()
                                        unmount()
                                    },
                                },
                                'Confirm',
                            ),
                        ]),
                    ]),
                ]),
            ],
        )

        const unmount = () => {
            const target = document.getElementById('confirmation-dialog')
            parent.removeChild(target)
        }

        render(child, parent)
    }

    return { confirm }
}

1

There are 1 best solutions below

5
Tolbxela On

You should do it using components. Something like this:

const { createApp, h } = Vue;

const confirm = {
  props: ['show', 'title'],
  emits: ['close'],
  setup(props, context) {    
    return () =>     
      !props.show ? h('div') :
      h('div',
            {
                id: 'confirmation-dialog',
                class: 'custom-popup-container',
            },
            [
                h('div', { class: 'custom-popup' }, [
                    h('div', { class: 'popup-header' }, [
                        h('h2', { class: 'popup-title' }, props.title),
                        h('a',
                            {
                                class: 'popup-close',
                                onClick: () => {
                                        alert('Cancel')
                                        context.emit('close', false)
                                    },
                            },
                            [h('span', { class: 'icon-close' })],
                        ),
                    ]),
                    h('div', { class: 'popup-content' }, [
                        h('p', {}, context.slots.default),
                    ]),
                    h('div', { class: 'pop-footer' }, [
                        h('div', { class: 'footer-btns' }, [
                            h('button',
                                {
                                    class: 'btn btn-lg btn-white',
                                    onClick: () => {                                        
                                        alert('Cancel')
                                        context.emit('close', false)
                                    },
                                },
                                'Cancel',
                            ),
                            h('button',
                                {
                                    class: 'btn btn-lg btn-primary',
                                    onClick: () => {
                                        alert('Confirm')
                                        context.emit('close', true)
                                    },
                                },
                                'Confirm',
                            ),
                        ]),
                    ]),
                ]),
            ],
        )
  }
}

const App = {
  components: { confirm },
  data() {
    return { showDialog: false, result: null }  
  },
  methods: {
    show() { 
      this.showDialog = true
    },
    close(value) { 
      this.showDialog = false; 
      this.result = value 
    },
  }  
}

const app = createApp(App)
const vm = app.mount('#app')
#app { line-height: 2; }
[v-cloak] { display: none; }
<div id="app" v-cloak>
showDialog: {{showDialog}}<br/>
result: {{result}}<br/>
<button @click="show()">confirm</button>
<hr/>
<confirm :show="showDialog" title="Please confirm" @close="close">Description</confirm>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>


UPDATE

Here the same as functional component

const { createApp, h } = Vue;

function confirm(props, context) {
    return h('div', 
      !props.show ? h('div') :
      h('div',
            {
                id: 'confirmation-dialog',
                class: 'custom-popup-container',
            },
            [
                h('div', { class: 'custom-popup' }, [
                    h('div', { class: 'popup-header' }, [
                        h('h2', { class: 'popup-title' }, props.title),
                        h('a',
                            {
                                class: 'popup-close',
                                onClick: () => {
                                        alert('Cancel')
                                        context.emit('close', false)
                                    },
                            },
                            [h('span', { class: 'icon-close' })],
                        ),
                    ]),
                    h('div', { class: 'popup-content' }, [
                        h('p', {}, context.slots.default),
                    ]),
                    h('div', { class: 'pop-footer' }, [
                        h('div', { class: 'footer-btns' }, [
                            h('button',
                                {
                                    class: 'btn btn-lg btn-white',
                                    onClick: () => {                                        
                                        alert('Cancel')
                                        context.emit('close', false)
                                    },
                                },
                                'Cancel',
                            ),
                            h('button',
                                {
                                    class: 'btn btn-lg btn-primary',
                                    onClick: () => {
                                        alert('Confirm')
                                        context.emit('close', true)
                                    },
                                },
                                'Confirm',
                            ),
                        ]),
                    ]),
                ]),
            ],
        )
    )
}

confirm.props = ['show', 'title']
confirm.emits = ['close']

const App = {
  components: { confirm },
  data() {
    return { showDialog: false, result: null }  
  },
  methods: {
    show() { 
      this.showDialog = true
    },
    close(value) { 
      this.showDialog = false; 
      this.result = value 
    },
  }  
}

const app = createApp(App)
const vm = app.mount('#app')
#app { line-height: 2; }
[v-cloak] { display: none; }
<div id="app" v-cloak>
showDialog: {{showDialog}}<br/>
result: {{result}}<br/>
<button @click="show()">confirm</button>
<hr/>
<confirm :show="showDialog" title="Please confirm" @close="close">Description</confirm>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>