Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue TransitionGroup + v-if + MobX computed property #132

Open
tpotjj opened this issue May 8, 2023 · 0 comments
Open

Vue TransitionGroup + v-if + MobX computed property #132

tpotjj opened this issue May 8, 2023 · 0 comments

Comments

@tpotjj
Copy link

tpotjj commented May 8, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant