Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

CamKem's avatar
Level 10

Watching for change of object on persistant layout.

Hello, I am trying to implement a way to watch if a flash message has been passed to the props, in a persistant layout view, so I can render a flash message. I am passing a an error message flashed to the session & passed to the client by inertia middleware (HandleInertiaRequests.php) when a user is not authorise ('auth' guard on the routes). The problem I am having is when a flash message has been mounted, rendered, then un-rendered/unmounted & I click on another link that is guarded, the flash message does not render again. To try and solve this I have installed Pinia & setup a store called useFlashStore. I know Pinia might not be nessisary here, but I am at a loose end trying to solve this. Here is my code:

FlashStore.js

import { defineStore } from "pinia";
export let useFlashStore = defineStore('flash', {
    state() {
        return {
            error: "",
            success: "",
            info: "",
            warning : "",
            show: false,
        };
    },
});

Layout.vue (persistent layout view that renders the flash message)

<script setup>
import Nav from "@/Shared/Nav.vue";
import {usePage} from "@inertiajs/vue3";
import {computed, ref, watch} from "vue";
import Sidebar from "@/Layouts/Sidebar.vue";
import FlashMessage from "@/Shared/FlashMessage.vue";
import {useFlashStore} from "@/stores/FlashStore.js";

let showBar = ref(true);

// get the flash object from the flash store
// then watch to see if the flash object is passed to the layout component
// if it is passed, set the flash object to the flash object that is passed
// if it is not passed, set the flash object to null

let flashWatch = useFlashStore();

let currentFlash = computed(() => {
    // if the flash object is passed to the layout component, return the flash object
    if (props.flash) {
        return props.flash;
    } else {
        return null;
    }
});

// watch to see if the flash object is passed each time the page is loaded
// if the flash message is passed to the layout, then set the pinia store (flashWatch)
// to the value of the flash object passed to the layout component.
// after 5 seconds make the Pinia store null.
watch(
    () => props.flash,
    (flash) => {
        console.log("flash object is passed to layout component");
        console.log(flash);
        flashWatch = flash;
        // set a timeout to remove the flash message after 5 seconds
        setTimeout(() => {
            flashWatch = null;
        }, 5000);
    }
);

let props = defineProps({
    flash: Object,
});

</script>

<template>
    <div class="flex flex-col h-screen">
        <header
            class="bg-white px-8 flex fixed w-screen z-40 items-center justify-between py-4 shadow-sm text-white">
            <h1>App Title</h1>
            <Nav/>
        </header>

        <main class="relative flex flex-grow">
            <main class="p-6 ml-8 mt-12 bg-gradient-to-b from-gray-200 via-gray-400 to-gray-600 flex-1">
                <div class="max-w-3xl h-fit mx-auto">
                    <slot/>
                </div>
            </main>
        </main>

        <footer
            class='px-8 flex items-center z-40 justify-between py-4 shadow-sm text-white bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-slate-500 to-sky-900'>
            <p class="text-sm">
                &copy; 2021 App Title
            </p>
        </footer>
    </div>

    <FlashMessage :flash="$page.props.flash" v-if="flashWatch" />
</template>

I know the watcher is working as I can see the console log each time I go to a different page, I just don't know how to make it so that the watcher makes the FlashMessage component render each time the watcher is triggered.

I appreciate your help on this one, thanks!

0 likes
1 reply
CamKem's avatar
CamKem
OP
Best Answer
Level 10

I solved it, not the most elegant solution but the flash message displays. The Layout watches for changes in the requests of the flash message object - this is the code in the Layout.vue:

import {useFlashStore} from "@/stores/FlashStore.js";

// get the flash object from the flash store
let flashWatch = useFlashStore();

// watch the flash object for changes
watch(
    () => props.flash,
    (flash) => {
        if (props.flash.error) {
            console.log("flash object is passed to layout component");
            flashWatch.error = props.flash.error;
            flashWatch.show = true;
        } else if (props.flash.success) {
            console.log("flash object is passed to layout component");
            flashWatch.success = props.flash.success;
            flashWatch.show = true;
        } else if (props.flash.warning) {
            console.log("flash object is passed to layout component");
            flashWatch.warning = props.flash.warning;
            flashWatch.show = true;
        } else if (props.flash.info) {
            console.log("flash object is passed to layout component");
            flashWatch.info = props.flash.info;
            flashWatch.show = true;
        }
    }
);

let props = defineProps({
    flash: Object,
});

Then then in the flash message, I read the store & check watch to see if the value of flash.show in the store is true, then render the message & hide it after 3 seconds. This is the code:

<template>
    <Teleport v-if="loaded" to="body">
        <Transition
            enter-from-class="ease-in-out opacity-0 scale-75 translate-y-12"
            enter-to-class="ease-in-out opacity-100 scale-100"
            enter-active-class="transition ease-in-out duration-300 translate-y-0"
            leave-from-class="ease-in-out opacity-100 scale-100 translate-y-0"
            leave-to-class="ease-in-out opacity-0 scale-75 translate-y-12"
            leave-active-class="transition ease-in-out duration-300"
        >
            <div v-if="flash.show">
                <div v-if="flash.success"
                     class="fixed bottom-4 right-4 text-white py-4 px-4 rounded-xl text-md hover:opacity-75 z-50 bg-[conic-gradient(at_top_right,_var(--tw-gradient-stops))] from-indigo-900 via-slate-900 to-teal-500">
                    {{ flash.success }}
                </div>
                <div v-else-if="flash.error"
                     class="fixed bottom-4 right-4 text-white py-4 px-4 rounded-xl text-md hover:opacity-75 z-50 bg-red-500">
                    {{ flash.error }}
                </div>
                <div v-else-if="flash.warning"
                     class="fixed bottom-4 right-4 text-white py-4 px-4 rounded-xl text-md hover:opacity-75 z-50 bg-yellow-500">
                    {{ flash.warning }}
                </div>
                <div v-else-if="flash.info"
                     class="fixed bottom-4 right-4 text-white py-4 px-4 rounded-xl text-md hover:opacity-75 z-50 bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-sky-400 to-indigo-900">
                    {{ flash.info }}
                </div>
            </div>
        </Transition>
    </Teleport>
</template>

<script setup>
import {onMounted, ref, watch} from "vue";
import {useFlashStore} from "@/Stores/FlashStore.js";

// create a ref to track if the component has been loaded
let flash = useFlashStore();

// create a ref to track if the component has been loaded
let loaded = ref(false);

// when the value in the store is set to true and the component is rendered, set loaded to true
onMounted(() => {
    // teleport the component to the body
    loaded.value = true;
    console.log("loading & teleporting component");
});

// watch the value of show and if it is true, set it to false after 3 seconds
watch(
    () => loaded.value,
    (track) => {
        if (track) {
            //hide the message after 3 seconds
            setTimeout(() => {
                // clear the value of the flash message
                flash.error = "";
                flash.success = "";
                flash.warning = "";
                flash.info = "";
                // hide the message
                flash.show = false;
                // unmount the component
                console.log("hiding message");
            }, 3000);
        }
    }
);

</script>

However, now my transitions will not work. I'll make a new post to see if anyone can help.

Please or to participate in this conversation.