Attempting to prevent altering a property nested three levels deep without using mutation, utilizing $emit instead

EDIT: Check out this repository that I created for easier parsing.

I have developed a Component that displays products in a datatable. The first column of the table contains a link that triggers a modal displaying a form specific to the product that was clicked (using its ID). To style and design my components, I am utilizing the PrimeVue library.

<template>
   <Column field="id" headerStyle="width: 5%">
     <template #body="slotProps">
        <ProductForm :product="slotProps.data" :show="showModal(slotProps.data.id)" />
           <a href="#" @click.stop="toggleModal(slotProps.data.id)">
              <span class="pi pi-external-link"> </span>
           </a>
     </template>
   </Column>
</template>

<script>
import ProductForm from "./forms/ProductForm";

export default {
  data() {
     return {
       activeModal: 0,
     }
  },
  components: { ProductForm },
  methods: {
    toggleModal: function (id) {
      if (this.activeModal !== 0) {
        this.activeModal = 0;
        return false;
      }
      this.activeModal = id;
    },
    showModal: function (id) {
      return this.activeModal === id;
    },
  },
</script>

The modal is actually nested within the ProductForm component (I created a template for the Modal for reusability). So essentially, there are three components in play (ProductList -> ProductForm -> BaseModal). Here's the structure of the product form:

<template>
  <div>
    <BaseModal :show="show" :header="product.name">
      <span class="p-float-label">
        <InputText id="name" type="text" :value="product.name">
        <label for="name">Product</label>
      </span>
    </BaseModal>
  </div>
</template>

<script>
import BaseModal from "../_modals/BaseModal";

export default {
  props: ["product", "show"],
  components: { BaseModal },
  data() {
    return {};
  },
};
</script>

When the modal pops up, it utilizes the ProductForm subcomponent. Below is the structure of the BaseModal component:

<template>
  <div>
    <Dialog :header="header" :visible.sync="show" :modal="true" :closable="true" @hide="doit">
      <slot />
    </Dialog>
  </div>
</template>

<script>
export default {
  props: {
    show: Boolean,
    header: String,
  },
  methods: {
    doit: function () {
      let currentShow = this.show;
      this.$emit("showModel", currentShow)
    },
  },
  data() {
    return {
    };
  },
};
</script>

I am passing the product object and a show boolean which determines the visibility of the modal all the way down through the components (from ProductList to ProductForm and finally to BaseModal). The modal being used is a PrimeVue component called Dialog. In this component, there is a property named "closable" that allows the modal to be closed using an X button when clicked, triggered by the hide event. Everything seems to be functioning correctly as expected apart from needing to click another modal link twice before it opens after the initial click.

The main issue arises when attempting to close a modal, resulting in the error message: "Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "show"". I have tried various approaches like emitting events and updating the original prop's value but the error persists (even with the existing code). Perhaps due to the depth of nesting reaching three components deep, my attempts are unsuccessful. As a newcomer to working with props, slots, and $emit, I understand that I may be making fundamental mistakes in my implementation. Additionally, structuring components at such a level of depth might also be contributing to the problem. What could I be overlooking?

Answer №1

It seems that the issue lies in emitting the showModel event from the BaseModal component without listening for it on the parent and forwarding it to the grandparent (ProductForm).

The problematic part is the use of :visible.sync="show" in the BaseModal. This is equivalent to using

:visible="show" @update:visible="show = $event"
(referenced in the documentation). When the Dialog is closed, PrimeVue triggers the update:visible event, which is caught by the BaseModal component (thanks to the .sync modifier), leading to a mutation of the show prop within the BaseModal and resulting in an error message...

Remember to avoid directly using props values with v-model or .sync

To resolve this issue, utilize the prop indirectly through a computed property with a setter:

BaseModal

<template>
  <div>
    <Dialog :header="header" :visible.sync="computedVisible" :modal="true" :closable="true">
      <slot />
    </Dialog>
  </div>
</template>

<script>
export default {
  props: {
    show: Boolean,
    header: String,
  },
  computed: {
    computedVisible: {
      get() { return this.show },
      set(value) { this.$emit('update:show', value) }
    }
  },
};
</script>

You can also implement a similar computed property in your ProductForm component and update the template to be

<BaseModal :show.sync="computedVisible" :header="product.name">
(so when the ProductForm receives the update:show event, it will emit the same event to its parent - as Vue events do not "bubble up" like DOM events, only the immediate parent component listens for the event)

Finally, handle the update:show event in the ProductList:

<ProductForm :product="slotProps.data" :show="showModal(slotProps.data.id)"  @update:show="toggleModal(slotProps.data.id)"/>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Vuetify throws an error: "TypeError: Vue.observable is not a valid function"

I'm encountering an issue while trying to install Vuetify in my Visual Studio 2019 project. I am using Vuetify version 2.3.4 along with vuetify-loader 1.6. While Vuetify seems to be working, as I can see styles applied to one of my v-btn tags, the con ...

Interacting with shadow DOM elements using Selenium's JavaScriptExecutor in Polymer applications

Having trouble accessing the 'shop now' button in the Men's Outerwear section of the website with the given code on Chrome Browser (V51)'s JavaScript console: document.querySelector('shop-app').shadowRoot.querySelector ...

Is there a way to resolve the code_challenge_method parameter not being allowed for this message type in Nuxt?

In my Nuxt project, I encountered an error with auth v5. This is the current strategy that I am utilizing: https://i.sstatic.net/DW06D.png Following the documentation (), it states that either "plain" or "S256" can be used as an option. I tried both opti ...

Common mistakes encountered when utilizing webpack for react development

I'm currently following the exercises in Pro MERN Stack by Apress and have come across a webpack issue. Everything was running smoothly until I introduced webpack into the mix. Now, when I execute npm run compile, I encounter the following error: > ...

Guide on Adding a Map to a List in JavaScript

Currently, I am trying to extract data from a form and add it as a map to my list. However, an error message is displayed: Cannot read property 'url' of undefined express = require("express"); app = express(); var bodyParser = require("body- ...

The jQuery AJAX function successfully executes, but the MySQL post deletion operation fails to take place

I am encountering an issue with this particular code. The Ajax code runs through to the end and then fades out the parent of the delete button. Below is the code for the delete button, post, and Ajax: <?php include('php/connect.php'); ...

Having trouble with exporting static HTML using Next.js

Recently, I have completed a project and now I am looking to export it to static HTML. In order to achieve this, I added the following command in my package.json file: build" : "next build && next export Upon running the command npm run ...

Is there a way I can incorporate v-for with a computed variable?

I am trying to display navigation items based on my authority and other conditions. Below is the code I am using: <template v-for="(subItem, index2) in item.children"> <v-list-item sub-group link :to="subItem.link" exact ...

The jQuery html() method and its use of ' ' characters

My issue involves a string that has unicode encoded nonbreaking space. I need to store this string in a hidden HTML element so that another function can access the value. The problem arises when using the html() function, as it seems to alter the content ...

Morphing BufferGeometry in Three.js

Can two buffer geometries be morphed in three.js? Are there any helpful examples to explore for reference? I am particularly keen on learning about manual morphing using morph target influences. ...

What is the best way to access JSON stringified objects in PHP?

I recently used the following code snippet to send data to the server, but now I'm stuck on how to retrieve the array that was returned using PHP. Any suggestions would be greatly appreciated. $('.ticket-row').each(function() { tickets.push ...

Can I send a JavaScript class to PHP as JSON in a different way?

As a beginner in this field, I am quickly learning and would greatly appreciate any kind of assistance. For example, I have an object like the following: function shape(name, size) { this.name = name; this.size = size; // additional function ...

Seeking specific parameter in a JSON array using JavaScript: A guide

Currently, I am working on a project that involves retrieving Facebook news feed data using the graph API. Upon receiving the JSON object, I display it on the page. The "Likes" section is presented as an array of JSON objects like this: data.likes: { ...

Tips for displaying CSS recommendations in JetBrains IDE (such as PyCharm) for your Nuxt project

Can anyone provide guidance on how to display CSS suggestions in JetBrains IDE (such as PyCharm)? They work fine in normal Vue projects with Vuetify, but for some reason they are not appearing in my Nuxt project. I currently have them working in my Vue pr ...

Using React Native to create a concise text component that fits perfectly within a flexbox with a

Within a row, there are two views with flex: 1 containing text. <View style={{ flexDirection: "row", padding: 5 }}> <View style={{ flex: 1 }}> <Text>Just a reallyyyyyyyy longgggg text</Text> </View> ...

Exploring the differences between scoping with let and without any scoping in

Within my code, there is a forEach loop containing a nested for loop. It's interesting that, even though I have a statement word = foo outside of the for loop but still inside the forEach loop, I can actually log the value of word after the entire for ...

start over with the route instead of simply tacking on another path to the existing one

Is there a way to create a router navigation system that prevents 404 errors when an invalid URL is entered? I found that manipulating a valid URL to an invalid one causes the next router link to stop working. To demonstrate, I have created a working examp ...

Choosing the Best Pattern for Utilizing NextJS with Django API

I recently migrated my project from VueJs to NextJs, hosted on Vercel, and I have GeoDjango running on a Digital Ocean droplet. In the Vue project, we used a service pattern connected with a store for fetching and updating data. While I've figured ou ...

JQuery Menu with Broken UL Formatting on Windows versions of IE and Firefox

Hey there! I've been struggling with a menu design that works perfectly on Mac browsers but is completely broken on Windows. I've spent hours trying to fix it and would really appreciate if a kind programmer could take a look and help me out. The ...

Developing a countdown clock with php and asynchronous javascript (ajax)

Can anyone provide guidance on how to create a real-time database timer? I have a starting time in the database and I am using PHP, looking to incorporate JS or AJAX for optimal functionality. Here is a rough outline of my plan: BEGIN Request server ti ...