How to share methods between unrelated components in Vue?

Viewed 239

I'm probably missing some concept of VueJS so I need help on this one. I made an example to make my question more clear. Let's say I have some multipurpose component like modal window. I'm grabbing data from Vuex.

./components/modal.vue

<script>
export default {
  methods: {
    modalClose() {
      this.$store.commit("modalClose");
    },
  },
  computed: {
    body() {
      return this.$store.state.modal.body;
    },
    footer() {
      return this.$store.state.modal.footer;
    },
    show() {
      return this.$store.state.modal.show;
    },
  },
};
</script>

<template>
  <div class="wrap" @click.self="modalClose" v-if="show">
    <div id="modal" class="modal">
      <div class="modal-body" v-html="this.body">body</div>
      <div class="modal-footer" v-html="this.footer">footer</div>
    </div>
  </div>
</template>

<style>
/* whatever */
</style>

And I have another component which set Vuex data and use my modal component to show the data. And it works fine - it presents the data. But at some point I need to work with the data inside the modal window and this is where I'm losing it.

./components/persons.vue


<script>
export default {
  data: function () {
    return {
      persons: [
        { name: "John Johnson", id: 1 },
        { name: "David Lynch", id: 2 },
        { name: "Marshall Attack", id: 3 },
      ],
    };
  },
  methods: {
    showModal: function (person) {
      this.$store.commit("modal", {
        body: "Name: " + person.name,
        footer: `<a href="#" person-id="${person.id}">Add</a>`,
      });
    },
    addPerson: function (id) {
      // how to trigger this function with "Add" button?
      console.log("Success! Person " + id + " was added!");
    },
  },
};
</script>
<template>
  <div id="page1">
    <div v-for="person in persons" :key="person.id">
      {{ person.name }}
      <a href="#" @click="showModal(person)"> Open modal </a>
    </div>
  </div>
</template>

In this example you can see method addPerson. It belongs to persons.vue and it suppose to be executed (argument required) with click on "Add" button inside the modal window. So, I have two questions:

  1. How can I do it?
  2. Am I populating bad practices?

P.S. I read about $emit but I didn't have any luck with it.

3 Answers

I did not fully understand your issue, but the basic idea is that various components can commit data to your store. And also dispatch actions to store. And store itself should be able to handle actions like addPerson using the data that is already committed. Usually in a case of multiple components that "create" an entry, your store (or a store module) would have

  • a state with relevant entries, for example in general CRUD it's usually an array of people and newPerson where you store the temporary entries
  • mutations that allow editing some of that data
  • a getter canAddPerson that computes if you have enough data in the newPerson object to actually create a new person
  • an action addPerson that uses newPerson, optionally does an AJAX POST, commits the new person to be included in the people array and clears the newPerson fields.

A caveat that I occasionally fall for is that this newPerson shouldn't be {}, it should instead be defined with all the fields {name: null, height: null, ...} otherwise the fields will not be reactive.

Am I populating bad practices?

You should not be putting HTML in the store, just data.

P.S. The answer to the question in title is "mixins", but I don't think that's what you've asked in the question body.

Lots of ways to skin the cat here.

  1. Modal component writes its own data to the store.

Since the modal already reads its data from the store, you could also set the store values directly from the modal. Create a new method that does this. The benefit here is the modal can read and write its own data. The downside is the modal is now tied to a certain type of data in your app and is not generally reusable.

  1. From persons.vue with $emit:

Your modal should $emit when a certain event happens, e.g. when the user clicks "Add". Wherever you have that link defined, add @click="$emit('add', whateverDataYouWantToReturn)" to it. Then in the parent component, listen for the event with @add="addPerson($event)" when you use the modal. The benefit here is you don't necessarily need to use the store; you can pass data to the modal as props and catch the events using $emit.

  1. Using a slot on the modal:

Whenever you use the modal in your parent template, you can insert whatever visual content you need inside the modal as supplied to the modal's slot. This has the advantage of keeping the modal component generally reusable while also allowing you to call methods on the parent.

Looking bad hook.

Normally: modal.vue be inside in persons.vue Then after modal show must be false in data properties (modalShow:false)

And you must send "props" to modal component if show modal with selected data.

Related