Running Vue 3.2.6 and Vite 2.5.1
I've been experimenting a bit with the new Composition API and trying to figure out some common usecases where it makes sense to use it in favor of the OptionsAPI. One good usecase I immediately identified would be in Modals, the little popups that occur with a warning message or dialogue or whatever else.
In my old Apps, I'd have to create the modal opening logic in every single component where the modal is being called, which lead to a lot of repetition. With the CompAPI, I tried extracting the logic into a simple modal.ts
file that exports 2 things, a reactive openModal
boolean, and a toggleModal
function. It works great! Until I have more than one modal in my app, that is, in which case it'll open every single Modal at once, on top of one another.
As an example setup
modal.ts
import { ref } from "vue";
const openModal = ref(false);
const toggleModal = () => {
openModal.value = !openModal.value;
};
export { openModal, toggleModal };
App.vue
<template>
<Example1 />
<Example2 />
<Example3 />
</template>
Modal.vue
<template>
<div class="modal" @click.self.stop="sendClose">
<slot />
</div>
</template>
<script setup>
const emit = defineEmits(["closeModal"]);
const sendClose = () => {
emit("closeModal");
};
</script>
Example#.vue
Note that each of these are separate components that have the same layout, the only difference being the number
<template>
<h1>Example 1 <span @click="toggleModal">Toggle</span></h1>
<teleport to="body">
<Modal v-if="openModal" @closeModal="toggleModal">
<h1>Modal 1</h1>
</Modal>
</teleport>
</template>
<script setup>
import { openModal, toggleModal } from "@/shared/modal";
import Modal from "@/components/Modal.vue";
</script>
What happens when clicking the toggle
span is obvious (in hindsight). It toggles the value of openModal
, which will open all 3 modals at once, one on top of the other. The issue is even worse if you try to implement nested Modals, aka logic in one modal that will open up another modal on top of that one.
Am I misunderstanding how to use ref
here? Is it even possible for each component to have and keep track of its own version of openModal
? Cause the way I've set it up here, it's acting more like a global store, which isn't great for this particular usecase.
The way I imagined this working is that each component would import the reactive openModal
value, and keep track of it independently. That way, when one component calls toggleModal
, it would only toggle the value inside of the component calling the function.
Is there a way of doing what I originally intended via the Composition API? I feel like the answer is simple but I can't really figure it out.
CodePudding user response:
That is because you are not exporting your composition correctly, resulting in a shared state, since you are exporting the same function and ref to all components. To fix your issue, you should wrap whatever you're exporting in modal.ts
in a function, say:
// Wrap in an exported function (you can also do a default export if you want)
export function modalComposition() {
const openModal = ref(false);
const toggleModal = () => {
openModal.value = !openModal.value;
};
return { openModal, toggleModal };
}
And in each component that you plan to use the composition, simply import it, e.g.:
import { modalComposition } from "@/shared/modal";
import Modal from "@/components/Modal.vue";
// By invoking `modalComposition()`, you are no longer passing by reference
// And therefore there is no "shared state"
const { openModal, toggleModal } = modalComposition();