Vue TransitionGroup + v-if + MobX computed property
Opened this issue · 0 comments
Now, this might not be an all-day case, and maybe I'm doing something wrong on my side, but here is the explanation of the issue:
In my code I have a 'ToastMessageSerive', which is a MobX store that holds some basic information regarding 'Toasts' that I want to display.
In this store, I generate a computed property based on an observable property, so far so good.
Now, if I want to use that computed property on a in order to determine the 'v-if', the transition won't work anymore...
Here is the code:
import { action, observable, computed, makeAutoObservable } from "mobx";
import { ToastMessage } from "~/models/response/ToastMessage";
export class ToastMessageService {
@observable
toastMessagesQueue: ToastMessage[] = [];
@computed
get toastMessageQueueLength(): number {
return this.toastMessagesQueue.length
}
@computed
get displayToastMessages(): ToastMessage[] {
return this.toastMessagesQueue.slice(0, 2);
}
@action.bound
addToast(toastMessage: ToastMessage) {
this.toastMessagesQueue.push(toastMessage);
if (this.toastMessagesQueue.length > 2) {
const lastToastMessage =
this.toastMessagesQueue[this.toastMessagesQueue.length - 2];
setTimeout(() => {
this.removeToast(toastMessage.id);
}, lastToastMessage.timeout + toastMessage.timeout + 1000);
} else {
setTimeout(() => {
this.removeToast(toastMessage.id);
}, toastMessage.timeout);
}
}
@action.bound
removeToast(toastMessageId: number) {
this.toastMessagesQueue = this.toastMessagesQueue.filter(
(toastMessage) => toastMessage.id !== toastMessageId
);
}
}
export const toastMessageService = new ToastMessageService();
makeAutoObservable(toastMessageService, {}, { autoBind: true });
&&
<template>
<Observer>
<!-- Global notification live region, render this permanently at the end of the document -->
<div
aria-live="assertive"
class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6"
>
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
<!-- Notification panel, dynamically insert this into the live region when it needs to be displayed -->
<TransitionGroup
v-if="toastMessageService.toastMessageQueueLength"
key="toast-messages"
enter-active-class="transform ease-out duration-300 transition"
enter-from-class="translate-x-full opacity-0"
enter-to-class="translate-x-0 opacity-100"
leave-active-class="transform ease-in duration-300 transition"
leave-from-class="translate-x-0 opacity-100"
leave-to-class="translate-x-full opacity-0"
>
<div
v-for="message in toastMessageService.toastMessagesQueue"
:key="message.id"
class="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5"
>
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<CheckCircleIcon
class="h-6 w-6 text-green-400"
aria-hidden="true"
/>
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium text-gray-900">
{{ toastMessageService.displayToastMessages }}
<br/>
<!-- {{ messages }} -->
</p>
<p class="mt-1 text-sm text-gray-500">
{{ message.message }}
</p>
</div>
<div class="ml-4 flex flex-shrink-0">
<button
type="button"
@click="toastMessageService.removeToast(message.id)"
class="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
<span class="sr-only">Close</span>
<XMarkIcon class="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
</div>
</TransitionGroup>
</div>
</div>
</Observer>
</template>
<script setup>
import { Observer } from "mobx-vue-lite";
import { CheckCircleIcon } from "@heroicons/vue/24/outline";
import { XMarkIcon } from "@heroicons/vue/20/solid";
import { toastMessageService } from "~/services/response/ToastMessageService";
import { ToastMessage } from "~/models/response/ToastMessage";
toastMessageService.addToast(
new ToastMessage({ id: 1, title: "Success", message: "Hoi", timeout: 3000 })
);
toastMessageService.addToast(
new ToastMessage({ id: 2, title: "Success", message: "Doei", timeout: 3000 })
);
toastMessageService.addToast(
new ToastMessage({
id: 3,
title: "Success",
message: "Ben ik weer",
timeout: 6000,
})
);
toastMessageService.addToast(
new ToastMessage({ id: 4, title: "Success", message: "Ciao", timeout: 6000 })
);
</script>
If I would determine the transitions 'v-if' this way, it will work (especially the messages computed value):
<script setup>
import { Observer } from "mobx-vue-lite";
import { CheckCircleIcon } from "@heroicons/vue/24/outline";
import { XMarkIcon } from "@heroicons/vue/20/solid";
import { toastMessageService } from "~/services/response/ToastMessageService";
import { ToastMessage } from "~/models/response/ToastMessage";
const messageQueue = ref([])
function removeToast(id) {
messageQueue.value = messageQueue.value.filter((item) => item.id !== id);
}
function addToast(toastConfig) {
messageQueue.value.push(toastConfig);
if (messageQueue.value.length > 2) {
const lastMessage = messageQueue.value[messageQueue.value.length - 2];
console.log(lastMessage);
setTimeout(() => {
removeToast(toastConfig.id);
}, lastMessage.timeout + toastConfig.timeout + 1000);
} else {
setTimeout(() => {
removeToast(toastConfig.id);
}, toastConfig.timeout);
}
}
const messages = computed(() => {
return messageQueue.value.slice(0, 2);
});
</script>
Is there something I'm missing?
Or is there something broken?
Btw, I use Nuxt and I have registered: buildModules: ["mobx-vue-lite/nuxt"].
I already used MobX somewhere else for storig values and nothing is wrong in that part of my code.