vue.jsvuejs3

VueJs emit event to parent template


I have a page that inherits from a base template (GuestLayout)

GuestLayout

<script>
import Toast from '../Components/Toast.vue'

export default {
    components: {
        Toast
    },
    data() {
        return {
            errors: []
        }
    },
    methods: {
        receive() {
            console.log('here')
        }
    }
}
</script>

<template>
    <main class="flex flex-col h-full">
        <Toast :errors="errors" />
        <slot name="nav" />
        <slot name="content" />
    </main>
</template>

Page

<script>
import GuestLayout from '../Layouts/Guest.vue'
import Header from '../Components/Header.vue'
import JsonResponseHelpers from '../Helpers/JsonResponse'

export default {
    components: {
        GuestLayout, Header
    },
    data() {
        return {
            jsonResponseHelpers: new JsonResponseHelpers(),
        }
    },
    methods: {
        async onLoginSubmit() {
            const response = await fetch('/login', {
                method: 'POST',
                body: JSON.stringify(this.forms.login),
            })
            const json = await response.json()
            if (!response.ok) {
                const errors = this.jsonResponseHelpers.flattenErrors(json.errors);
                for (const x of errors) {
                    this.$emit('receive', x)
                }
            }
        }
    },
}
</script>

<template>
    <GuestLayout>
        <template v-slot:nav>
            <Header />
        </template>
        <template v-slot:content>
            <div></div>
        </template>
    </GuestLayout>
</template>

I have removed some content to reduce the amount shown. I've reverted the script to its basic starting point, as I had been experimenting with it for a while to get it working as desired.

My question: If you look at the page script, I'm trying to emit a "receive" event within a loop. I want to capture this event in the base template. How can I do this?

I've read through the documentation and tried to find a solution for passing data to a template, not a component. I'm not sure if that distinction matters. It seems like scoped slots might be a solution, but I'm looking for a simpler approach. Additionally, dispatching a custom event might work, but is there a more idiomatic Vue way to do this?


Solution

  • In general, it's considered bad practice for a Layout to listen to emitted events, as this can lead to inconsistent behavior later on - for example, if you use the Layout with a component that forgets or intentionally doesn't emit the required event.

    From what I see, you want to pass data from the Page to the GuestLayout in order to display a toast message. In your structure, the Page is not a direct child of GuestLayout (since it's rendered via a slot), so this.$emit('receive') won't work. Again, having a Layout depend on emitted events is generally a poor practice, because it introduces tight coupling and makes reuse less predictable.

    A more common pattern for triggering toast messages is to provide globally available helper functions that trigger the toast component placed inside the Layout. However, that goes beyond the scope of your current question.

    Some of example for Toast working with external package: