This is a tool that makes using modals more convenient by using Vue 3 provide
/ inject
. Please familiarize yourself with it before use.
It provides a simpler way to toggle modals and pass data. It should reduce some of the work when configuring modals.
Supports Vue 3+.
Excludes the modal itself. If you're choosing a modal package, you might consider vue-final-modal。
npm install vue-use-modal-context
You can globally register the component <ModalContext>
, <ModalProvider>
using a plugin. (component: true
)
import { ModalContextPlugin } from "vue-use-modal-context"
const app = createApp(App)
app.use(ModalContextPlugin, { component: true })
app.mount('#app')
Alternatively, you can also import it directly.
import { ModalContext, ModalProvider } from "vue-use-modal-context"
First, let's take a look at a typical example of using a modal.
This is a situation I often encounter: to set up a modal, I need to configure show, openModal
, modalData
, and so on. If there are 10 modals, I have to set it up 10 times.
<script setup>
import { ref } from 'vue'
const show = ref(false)
const modalData = ref({
userId: 0
})
const openModal = (userId) => {
show.value = true
modalData.userId = userId
}
const onModalClose = () => {
modalData.userId = 0
}
</script>
<template>
<div>
<button @click="openModal(81383)">Show user</button>
<UserModal v-model="show" :user-id="modalData.userId" @close="onModalClose()">
<!-- Modal content -->
</UserModal>
</div>
</template>
Now, let's try using the tools provided by vue-use-modal-context
, namely useModalContext
and ModalProvider
, to simplify this example. First, call useModalContext()
, and you can obtain openModal
.
<!-- Step 1: call useModalContext -->
<script setup>
import { useModalContext, ModalProvider } from "vue-use-modal-context"
const { openModal } = useModalContext()
</script>
<template>
<div>
</div>
</template>
Next, you need to add ModalProvider
to register a modal with the modalContext
. We registered a modal with the name UserModal.
<!-- Step 2: use ModalProvider to register modal -->
<script setup>
import { useModalContext, ModalProvider } from "vue-use-modal-context"
const { openModal } = useModalContext()
</script>
<template>
<div>
<ModalProvider name="UserModal">
</ModalProvider>
</div>
</template>
Next, you can retrieve the modal's state from the scoped slot
of ModalProvider
, which includes show
and data
. Connect these two pieces of data to the modal and set the initial data to init-data
.
<!-- Step 3: connect ModalProvider to UserModal -->
<script setup>
import { useModalContext, ModalProvider } from "vue-use-modal-context"
const { openModal } = useModalContext()
</script>
<template>
<div>
<ModalProvider v-slot="{ modal }" name="UserModal" :init-data="{ userId: 0 }">
<UserModal v-model="modal.show" :user-id="modal.data.userId" />
</ModalProvider>
</div>
</template>
Finally, just add the button to open the modal and bind it to openModal
.
<!-- Step 4: openModal anywhere -->
<script setup>
import { useModalContext, ModalProvider } from "vue-use-modal-context"
const { openModal } = useModalContext()
</script>
<template>
<div>
<button @click="openModal('UserModal', { userId: 81383 })">Show user</button>
<ModalProvider v-slot="{ modal }" name="UserModal" :init-data="{ userId: 0 }">
<UserModal v-model="modal.show" :user-id="modal.data.userId" />
</ModalProvider>
</div>
</template>
If you want to add a new modal, you just need to register a new ModalProvider
.
<!-- Step ex: use more modal -->
<script setup>
import { useModalContext, ModalProvider } from "vue-use-modal-context"
const { openModal } = useModalContext()
</script>
<template>
<div>
<button @click="openModal('UserModal', { userId: 81383 })">Show user</button>
<button @click="openModal('OrderModal', { orderId: 233456 })">Show order</button>
<button @click="openModal('PaymentModal', { paymentId: 33445 })">Show payment</button>
<ModalProvider v-slot="{ modal }" name="UserModal" :init-data="{ userId: 0 }">
<UserModal v-model="modal.show" :user-id="modal.data.userId" />
</ModalProvider>
<ModalProvider v-slot="{ modal }" name="OrderModal" :init-data="{ orderId: 0 }">
<UserModal v-model="modal.show" :user-id="modal.data.orderId" />
</ModalProvider>
<ModalProvider v-slot="{ modal }" name="PaymentModal" :init-data="{ paymentId: 0 }">
<UserModal v-model="modal.show" :user-id="modal.data.paymentId" />
</ModalProvider>
</div>
</template>
useModalContext
also has a component version. If you prefer using components, you can give it a try as well.
<!-- Step ex: ModalContext component -->
<script setup>
import { ModalContext, ModalProvider } from "vue-use-modal-context"
</script>
<template>
<ModalContext v-slot="{ openModal, closeModal }">
<div>
<button @click="openModal('UserModal', { userId: 81383 })">Show user</button>
<button @click="openModal('OrderModal', { orderId: 233456 })">Show order</button>
<button @click="openModal('PaymentModal', { paymentId: 33445 })">Show payment</button>
<ModalProvider v-slot="{ modal }" name="UserModal" :init-data="{ userId: 0 }">
<UserModal v-model="modal.show" :user-id="modal.data.userId" />
</ModalProvider>
<ModalProvider v-slot="{ modal }" name="OrderModal" :init-data="{ orderId: 0 }">
<UserModal v-model="modal.show" :user-id="modal.data.orderId" />
</ModalProvider>
<ModalProvider v-slot="{ modal }" name="PaymentModal" :init-data="{ paymentId: 0 }">
<UserModal v-model="modal.show" :user-id="modal.data.paymentId" />
</ModalProvider>
</div>
</ModalContext>
</template>
You might need a global modal context that spans across components and contexts. In such cases, you can use useGlobalModalContext
.
To use global modal, you need register plugin. Please refer to document.
You can set <ModalProvider
> anywhere. Remember to set it as global
.
It is recommended to use it in the top-level component (e.g., App.vue
):
<!-- App.vue -->
<template>
<ModalProvider name="LoginModal" global>
<!-- Do anything just like non-global modal -->
</ModalProvider>
</template>
Then, you can use useGlobalModalContext
anywhere.
<!-- Child.vue -->
<script setup>
import { useGlobalModalContext } from "vue-use-modal-context"
const { openGlobalModal, patchGlobalModal, closeGlobalModal, } = useGlobalModalContext()
onMounted(() => {
openGlobalModal("LoginModal")
})
</script>
useModalContext
is a composition function. It uses the provide
to pass modal context data to child components. Additionally, it returns three functions:
-
openModal(name: string, data?: Record<string, any>) => void
: Opens the modal with the specified name and passes data. When passing data, it completely overwrites the existing data. Data is optional, but if provided, it must be an object. If not provided, the modal data will be the initial data. -
closeModal(name: string) => void
: Closes the modal with the specified name. -
patchModal(name: string, data: Record<string, any> | (data) => Record<string, any>) => void
:- Updates the data of the specified modal.
- It's not an overwrite but rather an update in a way similar to
Object.assign
. - It's recommended to perform patchModal after openModal.
- From
0.3.0
, data can be a update function, it receive a current modal data, and it return value will be merged to modal data.
// From 0.3.0
// patch modal by function data
patchModal("UserModal" ,(currentData) => {
return currentData.userId === 123 ? {
order: [
...currentData.order,
newOrder
]
} : {}
})
<ModalContext>
is the component version of useModalContext
, and it is a renderless component. These three functions can also be obtained in the scoped slot
of the <ModalContext>
component.
<ModalContext v-slot="{ openModal, closeModal, patchModal}">
<!--your content ....-->
</ModalContext>
You can use ref
to access it methods from setup
.
<script setup>
import { ref, onMounted } from 'vue'
const ModalContextRef = ref()
onMounted(() => {
if (ModalContextRef.value){
ModalContextRef.value.openModal('UserModal', { userId: 3310 })
// or closeModal, patchModal ....
}
})
</script>
<template>
<ModalContext v-slot="{ openModal, closeModal, patchModal}" ref="ModalContextRef">
<!--your content ....-->
</ModalContext>
</template>
<ModalProvider>
is the component version of useModalProvider
and is a renderless component. It registers the modal within its own ModalContext
.
It must be placed inside the ModalContext
. In other words, its parent component or a higher-level component must have either useModalContext
or <ModalContext>
.
<ModalProvider>
provides the following parameters:
name
: Required. Sets the name of the modal, corresponding to the first argument of openModal.init-data
: Optional. Determines the initial state ofmodal.data
. If not provided, it defaults to{}
.global
:boolean
. Specifies whether it belongs to the globalModalContext. The default isfalse
(see globalModalContext).reset-after-close
:boolean
. Determines whether to reset data back toinit-data
after closing. The default isfalse
.
<ModalProvider>
provides the following scoped slot
:
-
modal
: Contains the state of the modal and some functions. It is areactive
object, so do not destructure this object.modal.show
: boolean, indicating whether the modal is open or closed.modal.data
: Defaults to{}
, and ifinit-data
is provided, it defaults toinit-data
. When callingopenModal
, if data is passed,modal.data
becomes that data. You can also modifymodal.data
when callingpatchModal
.closeAnd(fn: () => any)
: Closes the modal and executesfn
. Useful when setting event handlers.
Here's an example of using closeAnd
:
<ModalProvider v-slot="{ modal, closeAnd }" name="UserEditModal" :init-data="{ userId: 0 }">
<Modal v-model="modal.show">
<UserForm :user-id="modal.data.userId" @update-sucess="closeAnd(fetchUserList)" />
</Modal>
</ModalProvider>
useModalProvider
is a composition function。<ModalProvder>
use useModalProvider
internally.
params:(name: string, initData: Record<string, any> = {}, resetAfterClose: boolean = false, global: boolean = false)
name
: Required. Sets the name of the modal, corresponding to the first argument ofopenModal
.initData
: Optional. Determines the initial state ofmodal.data
. If not provided, it defaults to{}
.resetAfterClose
:Optional
. Specifies whether to reset data back toinitData
after closing. The default isfalse
.global
:boolean
. Specifies whether it belongs to the globalModalContext. The default is false (see globalModalContext).
Returns:
modal
: Same as the modal in the scoped slot.
useGlobalModalContext
works the same as useModalContext
, but manipulate global context. it will returns:
openGlobalModal
: Similar to openModal, but can only open global modals.closeGlobalModal
: Similar to closeModal, but can only close global modals.patchGlobalModal
: Similar to patchModal, but can only patch global modals.
Most types in this package are exposed. The following explains the most commonly used type settings.
When you are using globally registered components (e.g: plugin), if you are using vscode + volar
, you can set the global components type through the following way:
// src/components.d.ts
import { ModalContext, ModalProvider } from 'vue-use-modal-context'
declare module 'vue' {
export interface GlobalComponents {
ModalContext: typeof ModalContext
ModalProvider: typeof ModalProvider
}
}