Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
October 26, 2022 05:37 pm GMT

Reusable dynamic modal on Vue 3

Reusable dynamic modal on Vue 3

Most of the time on frontend development the best way to keep a consistent way of building components is trying to make them reusable every time we can, but sometimes the framework itself can make it a bit hard if we dont have deep knowledge of its internal API, specifically the way it handles view instance and state component data.

What makes a good reusable modal?

  • Dynamic views
  • Customizable actions with callbacks (buttons)
  • easy-peasy instantiation

For simplicity we will be using:

Lets start by defining a very basic modal template using daisy classes:

<!-- @/components/x-modal.vue --><template>  <div>    <div class="modal modal-open">      <div class="modal-box relative">        <label class="btn btn-sm btn-circle absolute right-2 top-2"></label>        <h1>Hey, it works </h1>      </div>    </div>  </div></template>

To be able to access it from anywhere and render any type of view, the modal needs to be accessible from any component that wants to use it, for this reason the modal component needs to be placed on the root parent view where all the views will be rendered, assuming Vue router is being used:

<template>  <x-modal />  <router-view /></template><script lang="ts" setup>  import XModal from "@/components/x-modal.vue";</script>

You should be able to see our beautiful modal rendered!

Setup the store (state management)

As it's a reusable global modal, we need to keep global track if this modal is opened or closed and also have the ability to control it from any other component, maintaining a unique instance. This is where Pinia comes in.

Lets define a new store specifically to interact with the modal:

// @/store/modal.tsimport { markRaw } from "vue";import { defineStore } from "pinia";export type Modal = {  isOpen: boolean,  view: object,  actions?: ModalAction[],};export type ModalAction = {  label: string,  callback: (props?: any) => void,};export const useModal = defineStore("modal", {  state: (): Modal => ({    isOpen: false,    view: {},    actions: [],  }),  actions: {    open(view: object, actions?: ModalAction[]) {      this.isOpen = true;      this.actions = actions;      // using markRaw to avoid over performance as reactive is not required      this.view = markRaw(view);    },    close() {      this.isOpen = false;      this.view = {};      this.actions = [];    },  },});export default useModal;

Now we have the store, lets connect it to our modal template so we can use its reactive references:

<template>  <div>    <!-- isOpen is reactive and taken from the store, define if it is rendered or not -->    <div v-if="isOpen" class="modal modal-open">      <div class="modal-box relative">        <!-- @click handles the event to close the modal calling the action directly in store -->        <label          class="btn btn-sm btn-circle absolute right-2 top-2"          @click="modal.close()"          ></label        >        <!-- dynamic components, using model to share values payload -->        <component :is="view" v-model="model"></component>        <div class="modal-action">          <!-- render all actions and pass the model payload as parameter -->          <button            v-for="action in actions"            class="btn"            @click="action.callback(model)"          >            {{ action.label }}          </button>        </div>      </div>    </div>  </div></template><script lang="ts" setup>  import { reactive } from "vue";  import { storeToRefs } from "pinia";  import { useModal } from "@/stores";  const modal = useModal();  // reactive container to save the payload returned by the mounted view  const model = reactive({});  // convert all state properties to reactive references to be used on view  const { isOpen, view, actions } = storeToRefs(modal);</script>

<component> is the way that vue handle dynamic components where :is receive the view that needs to be rendered, more info here.

It also support using the v-model directive to be able to share values dynamically, but how does this works?

Once we have the view mounted inside <component> we can make use of v-model to send and update reactive references emitting events from the view we are mounting. Internally from the view we want to render: emit a "update:modelValue" event that will update the reference passed via v-model back, eg:

<!-- MyViewToRender.vue --><script lang="ts" setup>  import { watch } from "vue";  // no need to import defineEmits  const emit = defineEmits(["update:modelValue"]);  // when someVar changes, it will update the reference passed via v-model  watch(someVar, (value) => {    emit("update:modelValue", value);  });</script>

Make use of the modal

To be able to control and populate data into the modal, we'll use the store defined as follow:

<template>  <div>    <button class="btn" @click="handleOnClickOpenModal">Open</button>  </div></template><script lang="ts" setup>  import useModal from "@/stores/modal";  import MyViewToRender from "@/views/MyViewToRender.vue";  const modal = useModal();  function handleOnClickOpenModal() {    modal.open(MyViewToRender, [      {        label: "Save",        callback: (dataFromView) => {          console.log(dataFromView);          modal.close();        },      },    ]);  }</script>

When a click event on the button occurs, it will call handleOnClickOpenModal which at the same time will call the open action from the store, which changes the isOpen variable from the state, and as the modal is observing this value it will render or not depending of the value as follow:

Hurra!!! you have made a reusable modal on top of composition API and pinia.

Dynamic modal on vue 3

What if we want to pass a payload to our view?

Dependency injection is a programming technique / design pattern in which an object or function receives other objects or functions that it depends on.

Vue natively implements a mechanism to handle this pattern, this implementation is known as Provide / Inject, more info here. A parent component can serve as a dependency provider for all its descendants. Any component in the descendant tree, regardless of how deep it is, can inject dependencies provided by components up in its parent chain. The issue with this approach is that it only applies to direct descendants from parent (not parallel components) and as the modal view is not strictly a child of the component invoking it, it won't work as intended.

Another way is to add a field property to the state of the modal on pinia (payload) and pass any reactive references from there, and then receive that object from the view we render.

Hope it helps, cheers


Original Link: https://dev.to/cloudx/reusable-dynamic-modal-on-vue-3-1k56

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To