The simplest way to deal with modal dialogs in Vue 3
Modal dialogs are not such a difficult task to develop. They are often used to confirm user's action. For example, delete any data or perform authorization. This is a very monotonous and uninteresting job with repetitive logic that is sometimes copied from component to component with minor changes.
But what if you have dozens or even hundreds of similar dialogs on one page or even in the entire project? Or if you need to call dialogs in a chain depending on the user's choice? How to make a reusable function which also has readable and maintainable code?
It would be useful to create a function that would take a dialog component and control its rendering in the template. The function would return an object containing the state of the dialog and methods, so that it could be possible to work with it like with promises. For example, this plugin vue-modal-dialogs implements this functionality. Unfortunately, it has not been updated for a long time and does not support Vue 3.
In this guide I will introduce the vuejs-confirm-dialog plugin and show how to use it. I'll start with simple examples and end up with a function which completely abstracts the popup dialog to confirm an action and can be used in any component of your project. The examples are written in JavaScript
for easy reading, but the plugin itself is in TypeScript
. The plugin is fully typed and documented, which makes it much easier to work with it. Unlike vue-modal-dialogs
, this plugin has additional functionality - special hooks: onConfirm
and onCancel
. They accept a callback and are called depending on the user's decision: onConfirm
if the user agrees and onCancel
if he refuses.
The end result can be seen in the sandbox. The code is slightly different from what is in the post.
Installation
Let's start by creating a new Vue 3 project. In the console:
vue create dialogs-guide? Please pick a preset: // Pick a second option Default ([Vue 2] babel, eslint)> Default (Vue 3) ([Vue 3] babel, eslint) Manually select features
We have received a standard template for a new project. Next, go to the project folder and install the library according to the documentation in README.md.
npm i vuejs-confirm-dialog
Replace the code in main.js
with this:
import { createApp } from 'vue'import App from './App.vue'import * as ConfirmDialog from 'vuejs-confirm-dialog' createApp(App).use(ConfirmDialog).mount('#app')
Usage
Now let's move on to the App.vue file. Let's fix the template code first. We have to add the <DialogsWrapper/>
component to the template, to make the plugin work, and remove the HelloWord
:
<template> <img alt="Vue logo" src="./assets/logo.png"> <DialogsWrapper /></template>
Now let's learn how to use the createConfirmDialog
function. Use the new setup
syntax for the script
section. createConfirmDialog
accept as the first argument a component that will be the modal dialog and the second will be an object which contains component props values. The function returns an object with methods for working with the modal window, so the reveal
method renders the dialog box and onConfirm
accepts the callback which will be called if the user clicks "agree". You can create a HelloWord
component using the logo and pass the value of the msg
prop:
<template> <img alt="Vue logo" src="./assets/logo.png" @click="reveal"> <DialogsWrapper /></template> <script setup>import HelloWorld from './components/HelloWorld.vue'import { createConfirmDialog } from 'vuejs-confirm-dialog' const { reveal } = createConfirmDialog(HelloWorld, { msg: 'Hi!'})</script>
No additional logic is required. The component is rendered after calling the reveal
function and disappears after the user responds to the dialog.
Real life example
Now let's write something closer to real use.
Let's create a new component SimpleDialog.vue
in the components
folder:
<template> <div class="modal-container"> <div class="modal-body"> <span class="modal-close" @click="emit('cancel')">๐</span> <h2>{{ question }}</h2> <div class="modal-action"> <button class="modal-button" @click="emit('confirm')">Confirm</button> <button class="modal-button" @click="emit('cancel')">Cancel</button> </div> </div> </div></template>
Notice that two incoming events need to be added to the modal dialog to work properly: ['confirm', 'cancel']
.
And now we use it to confirm an action, for example, to hide the logo. The logic of the code, which will be executed after the user's consent, will be placed in the onConfirm
hook callback.
<template> <img v-show="showLogo" alt="Vue logo" src="./assets/logo.png"> <button @click="reveal">Hide Logo</button> <DialogsWrapper /></template> <script setup>import SimpleDialog from './components/SimpleDialog.vue'import { createConfirmDialog } from 'vuejs-confirm-dialog'import { ref } from 'vue' const showLogo = ref(true) const { reveal, onConfirm } = createConfirmDialog(SimpleDialog, { question: 'Are you sure you want to hide the logo?'}) onConfirm(() => { showLogo.value = false})</script>
Reusing
What if we have many cases where confirmation of some action is required? Do we need to call createConfirmDialog
again every time?
No. It is possible to write a function which automates the process for us.
import SimpleDialog from './../components/SimpleDialog'import { createConfirmDialog } from 'vuejs-confirm-dialog' const useConfirmBeforeAction = (action, props) => { const { reveal, onConfirm } = createConfirmDialog(SimpleDialog, props) onConfirm(action) reveal()} export default useConfirmBeforeAction
Now we use it to confirm the following external links:
<template> <ul> <li v-for="(link, i) in LINKS" @click="goToLink(link)" :key="i"> {{ link }} </li> </ul> <DialogsWrapper /></template> <script setup> import useConfirmBeforeAction from './composables/useConfirmBeforeAction' const LINKS = [ 'https://vuejs.org/', 'https://github.com/', 'https://vueuse.org/', ] const goToLink = (link) => { useConfirmBeforeAction( () => { window.location = link }, { question: `Do you want to go to ${link}?` } ) } </script>
Conclusion
The createConfirmDialog
function makes it easier to work with modal windows, reuse logic, and create chains of sequential dialogs. It takes care of rendering the modal window, passing incoming parameters to the component, and receiving data from it. It is very flexible and easy to customize it according to your needs.
These are not all its possibilities. For example, if the concept of hooks is not close to you, you can replace them with a promise that returns reveal
.
And of course, for a better dev experience use it with TypeScript!