N
Nuxt5d ago
thaekeh

Clean Modal emits

Hi all, I'm making the transition to Nuxt UI v3 (and am really enjoying it so far). There is one issue that I'm curious to see if someone solved in a nicer way. Sometimes a Modal can take multiple actions, for which I emit "close" events with the relevant data and a type field like type: "login". This feels a bit messy, so I'm curious if anyone has other ideas. Also, the parent component doesn't see the types of the emit, so it's a bit annoying to work with IMO. Does anyone already have a solution for this? Thanks in advance!
2 Replies
kapa.ai
kapa.ai5d ago
Hi @thaekeh. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- I understand your challenge with handling multiple actions in Nuxt UI v3 modals. Based on the knowledge sources, there are a couple of approaches you can consider. Modal Emits in Nuxt UI v3 According to the documentation, the Modal component in Nuxt UI v3 has two main emits: - update:open - Emitted when the modal's open state changes - after:leave - Emitted after the modal's leave transition completes Modal Emits Documentation Recommended Approaches 1. Using the useOverlay Composable The most elegant solution appears to be using the useOverlay composable, which allows you to programmatically control modals and capture return values:
<!-- ModalExample.vue -->
<script setup lang="ts">
defineProps<{
// Your props here
}>()

// Define typed emits
const emit = defineEmits<{ close: [boolean | { type: string, data: any }] }>()
</script>

<template>
<UModal :close="{ onClick: () => emit('close', false) }">
<template #footer>
<div class="flex gap-2">
<UButton label="Login" @click="emit('close', { type: 'login', data: loginData })" />
<UButton label="Register" @click="emit('close', { type: 'register', data: registerData })" />
</div>
</template>
</UModal>
</template>
<!-- ModalExample.vue -->
<script setup lang="ts">
defineProps<{
// Your props here
}>()

// Define typed emits
const emit = defineEmits<{ close: [boolean | { type: string, data: any }] }>()
</script>

<template>
<UModal :close="{ onClick: () => emit('close', false) }">
<template #footer>
<div class="flex gap-2">
<UButton label="Login" @click="emit('close', { type: 'login', data: loginData })" />
<UButton label="Register" @click="emit('close', { type: 'register', data: registerData })" />
</div>
</template>
</UModal>
</template>
Then in your parent component:
<script setup lang="ts">
import { LazyModalExample } from '#components'

const overlay = useOverlay()
const modal = overlay.create(LazyModalExample)

async function openModal() {
const result = await modal.open()

if (result && typeof result === 'object') {
if (result.type === 'login') {
// Handle login
} else if (result.type === 'register') {
// Handle register
}
}
}
</script>
<script setup lang="ts">
import { LazyModalExample } from '#components'

const overlay = useOverlay()
const modal = overlay.create(LazyModalExample)

async function openModal() {
const result = await modal.open()

if (result && typeof result === 'object') {
if (result.type === 'login') {
// Handle login
} else if (result.type === 'register') {
// Handle register
}
}
}
</script>
This approach is documented in the Programmatic Usage section and provides type safety through the defineEmits generic. 2. Using Multiple Specific Emits If you prefer to stick with the v-model approach, you could define multiple specific emits:
<script setup lang="ts">
const emit = defineEmits(['update:open', 'login', 'register'])

function handleLogin() {
emit('login', loginData)
emit('update:open', false)
}

function handleRegister() {
emit('register', registerData)
emit('update:open', false)
}
</script>
<script setup lang="ts">
const emit = defineEmits(['update:open', 'login', 'register'])

function handleLogin() {
emit('login', loginData)
emit('update:open', false)
}

function handleRegister() {
emit('register', registerData)
emit('update:open', false)
}
</script>
TypeScript Support For better TypeScript support, make sure to use the generic version of defineEmits to properly type your events:
const emit = defineEmits<{
'update:open': [value: boolean]
'login': [payload: LoginData]
'register': [payload: RegisterData]
}>()
const emit = defineEmits<{
'update:open': [value: boolean]
'login': [payload: LoginData]
'register': [payload: RegisterData]
}>()
The useOverlay approach is generally cleaner as it allows you to await the result directly rather than setting up multiple event handlers in the parent component. Hope this helps with your transition to Nuxt UI v3! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai5d ago
<@507571536161669136> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?