skip to Main Content

When i started this project a created a reusable global window component using a Pinia store. It has worked beautifully for me and I haven’t had any problems with it while working on the project for 2 months. All of my modals have been forms that don’t need acces to any outside reactive varaibles.

The problem:

I need a way to extend my component in a way so it can handle Vue event emits. This way the component is able to communicate to outside components.

The current component and its Pinia store:

import { defineStore } from "pinia";
import { ref } from "vue";
import { markRaw } from "vue";

const defaultState = { component: null, props: {} };

export const useModalStore = defineStore("modal", () => {
  const modalState = ref(defaultState);

  const openModal = ({ component, props }) => {
    modalState.value = { component: markRaw(component), props: props || {} };
  };

  const closeModal = () => {
    modalState.value = defaultState;
  };

  return { modalState, openModal, closeModal };
});
<script setup>
import { useModalStore } from "@stores/ModalStore";
import { storeToRefs } from "pinia";

const { closeModal } = useModalStore();
const { modalState } = storeToRefs(useModalStore());
</script>

<template>
  <Teleport to="#modals">
    <Transition name="modal-window-transition">
      <div
        class="modal-window__wrapper"
        @click.self="closeModal"
        v-if="modalState?.component"
      >
        <div class="modal-window">
          <component :is="modalState?.component" v-bind="modalState?.props" />
        </div>
      </div>
    </Transition>
  </Teleport>
</template>

How is the component used?

First import the modalStore and the component u want in the modal. Then extract the openModal and or closeModal actions from the store. Call the appropriate action when for example a click event occours. You can pass props just fine using the props param.

import { useModalStore } from "@stores/ModalStore";

import BaseButton from "@components/BaseButton.vue";
import DishesModalCreate from "@components/DishesModalCreate.vue";

<BaseButton @click="openModal({ component: DishesModalCreate })"
  >Add dish
</BaseButton>

I’ll try to explain the problem I want to solve using an example. How can the ModalContent component update the parentValue ref trough an event.

Parent.vue
import { useModalStore } from "@stores/ModalStore";
import { ref } from "vue"

import ModalContent from "@components/ModalContent.vue";

const { openModal } = useModalStore();

const parentValue = ref("update me via modal")

<template>
  <button @click="openModal({component: ModalContent})"></button>
</template>
ModalContent.vue
<script setup>
const emits = defineEmits(['updateParentState'])
</script>

<template>
  <button @click="emit("updateParentState")"></button>
</template>

Possible solutions

I know you can pass functions as props in Vue but its considered as a bad practice that if possible I want to avoid.

Another option would be to use global state but That’s a bit overkill imo.

Hope this gives enough context to understand what I’m trying to achieve. Any insights are welcome thanks in advance!!

2

Answers


  1. Chosen as BEST ANSWER

    For my use case i wanted to update the contents of a delivery so i passed the onUpdateOutboundDeliveryContents prop to my modal that triggers the updateOutboundDeliveryContents function in the parent component. In the modal itself I defined the event and then emit it when a button is clicked.

    Thanks for the help and if there is something I misunderstood you can definetly let me know!

    const emits = defineEmits(["updateOutboundDeliveryContents"]);
    
    <BaseButton
      @click="$emit('updateOutboundDeliveryContents')"
      >Add to outbound delivery
    </BaseButton>
    
    props: {
      machine: machine,
      onUpdateOutboundDeliveryContents: updateOutboundDeliveryContents,
    },
    
    const updateOutboundDeliveryContents = (content) => {
      console.log("updating...");
    };
    

  2. It’s possible to pass callbacks through props where necessary in Vue but here event bus still can be used. Event listeners can be passed altogether with v-bind, their names are universally processed to match to the convention used in render functions; onFoo prop is processed the same way as @foo event handler.

    openModal({ component: ModalContent, props: { onUpdateParentState: someFn } })
    

    Where someFn is supposed to be triggered on emit("updateParentState") inside ModalContent

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search