javascriptvue.jsvuejs3compositionvue-composition-api

Multiple composable instances within a single script tag in Vue 3


I'm currently undertaking a rewrite of our components that are currently written with the Options API. One interesting point for a rewrite from a code-cut standpoint is our many modals, which all have their own closing/opening and boolean logic attached everywhere they're present.

My issue is that I'm having a hard time figuring out how to get a composable function to work for more than 1 instance of a modal.

Take this extremely simple example:

Modal.vue

<template>
  <div v-if="isOpen" @click="$emit('closeModal')"> 
    <slot></slot>
  </div>
</template>
const props = defineProps<{ isOpen: false }>();

useModal.ts

export default const useModal = () => {
  const isModalOpen = ref(false);
  const toggleModal = () => isModalOpen.value = !isModalOpen.value;

  return { isModalOpen, toggleModal }
}

And to use this in a component somewhere, you'd do something like this

Component.vue

<template>
  <button @click="toggleModal">Open modal<button>
  <Modal :is-open="isModalOpen" @close-modal="toggleModal">Modal Content</Modal>
</template>
import useModal from "useModal";

const { isModalOpen, toggleModal } = useModal();

This works just fine when it's just one Modal on the page, but how can I get this to properly work with any arbitrary number of Modals on the page? Obviously I'd still need to define the variable name to keep track of which modal gets toggled/is opened, but how do I do this without basically just recreating the content of the useModal composition for each and every modal on the page?

Ideally I'd want to do something similar to this

<template>
  <button @click="toggleOne">Open 1</button>
  <button @click="toggleTwo">Open 2</button>

  <Modal :is-open="isOneOpen" @close-modal="toggleOne">Modal 1</Modal>
  <Modal :is-open="isTwoOpen" @close-modal="toggleTwo">Modal 2</Modal>
</template>
import useModal from "useModal";

const { isOneOpen, toggleOne } = useModal();
const { isTwoOpen, toggleTwo } = useModal();

But this doesn't work (somewhat obviously). Is there a way for me to achieve what I'm aiming for here, or am I fundamentally misunderstanding how/when to use composables?

I've tried some varieties like this

const { isModalOpen as isOneOpen } = useModal();

const isOneOpen = useModal().isModalOpen;

const isOneOpen = { ...useModal().isModalOpen; };

But none of these worked for me.


Solution

  • You can reassign the names of the composable's return values during the object destructure like this:

    const { isModalOpen: modalOne, toggleModal: toggleModalOne } = useModal();
    

    That should be enough to differentiate which modal's state to track.

    Working example here: https://codesandbox.io/s/vue-3-composition-destructuring-fts2x9