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
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!
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.Where
someFn
is supposed to be triggered onemit("updateParentState")
insideModalContent