Vue JS "PROP MUTATING" for Upload Image - javascript

first sorry for my bad English.
I have one component, this component only working for upload image.
I'm running this component to 2 form. First add form, second edit form. Edit modal open and send to props Image URL.
This..
<ua-single-upload :propsImage="editSingleImage" #uploadImage="addSingleImage = $event"></ua-single-upload>
This is so good working. Image:
If I'm reload new photo, working and console give this error: "[Vue warn]: 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: "propsImage""
AND...
This component not working with ADD FORM. I select image, not showing not uploading...
Please help me friends..
I want to be able to add a new image and update the existing one with a component.
This is my Component Codes...
<template>
<div class="singleImageUpdate p-4">
<div class="p-4">
<h4>Kapak Fotoğrafı Seçiniz</h4>
</div>
<div class="p-4">
<input
type="file"
name="fileUrl"
id="file"
ref="fileInput"
#change="onFileChange" />
<label for="file">Yeni Fotoğraf Ekle</label>
<button
class="ml-4"
type="button"
v-if="this.propsImage != null"
#click="onFileDelete"> Fotoğrafı Kaldır </button>
<button
class="ml-4"
type="button"
v-else
disabled
#click="onFileDelete"> Fotoğrafı Kaldır </button>
</div>
<div class="p-4 mt-4">
<small v-if="this.propsImage">
Fotoğraf kırpılmamaktadır, görüntü temsilidir.
</small>
<img
class="mt-4 shadow-lg"
v-if="this.propsImage"
:src="propsImage" />
</div>
</div>
</template>
<script>
export default{
data(){
return{}
},
props: {
propsImage: String
},
methods: {
onFileChange(event) {
const file = event.target.files[0];
this.propsImage = URL.createObjectURL(file);
this.$emit("updateSingleImage", 1);
this.$emit("uploadImage",event.target.files[0]);
},
onFileDelete() {
this.propsImage = "";
const input = this.$refs.fileInput;
input.type = "text";
input.type = "file";
this.$emit("updateSingleImage", 0);
this.$emit("uploadImage", null);
},
}
}

Id say the warning is pretty descriptive, you are mutating the property directly which is a bad practice, since the parent might change the prop value and will therefore overwrite it.
What you should do instead is perhaps:
Create a reactive property inside the data function and use the prop as an initial value:
props: {
propsImage:string
},
data(){
return {
image: this.propsImage
}
}
or if you want to update image whenever propsImage changes:
watch: {
propsImage(newValue){
this.image = newValue
}
}
or If you want to update the prop in the parent component emit the event
computed: {
image: {
get(){
return this.propsImage
},
set(newValue)
{
this.$emit('update:props-image',newValue)
}
}
}
and change the property inside the template of the parent component to <my-component :props-image.sync="myValue" />
Also there is no this context bound to the vue instance in the template is there?

Related

Trying to avoid mutating a prop that goes 3 levels deep by using $emit

EDIT: Here's a repo I made for easier parsing.
I have a Component that lists products in a datatable. The first column of the table is a link that shows a modal with a form of the product that was clicked (using its ID). I'm using the PrimeVue library for styling and components.
<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 a sub component of the ProductForm component (I made a template of the Modal so I could reuse it). So it's 3 components all together (ProductList -> ProductForm -> BaseModal). Here's 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 uses the ProductForm subcomponent. Here is 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'm passing the product object, and a show boolean that designates if the modal is visible or not from the first component (ProductList) all the way down through the ProductForm component and finally to the BaseModal component. The modal is a PrimeVue component called Dialog. The component actually has it's own property called "closable" which closes the modal with an X button when clicked, that is tied to an event called hide. Everything actually works. I can open the modal and close it. For some reason I have to click the another modal link twice before it opens after the initial.
The issue is when I close a modal, I get the 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" error. I've tried everything to emit to the event and change the original props value there, but the error persists (even from the code above) but I'm not sure if because I'm 3 components deep it won't work. I'm pretty new to using props and slots and $emit so I know I'm doing something wrong. I'm also new to laying out components this deep so I might not even be doing the entire layout correctly. What am I missing?
Well you are emitting the showModel event from BaseModal but you are not listening for it on the parent and forwarding it+listening on grandparent (ProductForm)
But the main problem is :visible.sync="show" in BaseModal. It is same as if you do :visible="show" #update:visible="show = $event" (docs). So when the Dialog is closed, PrimeVue emits update:visible event which is picked by BaseModal component (thanks to the .sync modifier) and causes the mutation of the show prop inside BaseModal and the error message...
Remember to never use prop value directly with v-model or .sync
To fix it, use the prop indirectly via a computed with the 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>
Now you can add same computed into your ProductForm component and change the template to <BaseModal :show.sync="computedVisible" :header="product.name"> (so when the ProductForm receives the update:show event, it will emit same event to it's parent - this is required as Vue event do not "bubble up" as for example DOM events, only immediate parent component receives the event)
Final step is to handle update:show in the ProductList:
<ProductForm :product="slotProps.data" :show="showModal(slotProps.data.id)" #update:show="toggleModal(slotProps.data.id)"/>

VueJS: Method is not defined on the instance

I made a component for a drag and drop element. What I want to do is use my .vue template file, instead of writing all the HTML on the single line after the template. (When I do write it in the component itself, it works fine, but just doesn't look good.) I need it to be a component because it can be that there is more than one component on one page.
Here is my .js file:
import Vue from 'vue';
import SingleFileUpload from './single-file-upload.vue'
Vue.component ('test', SingleFileUpload, {
delimiters: ['${', '}'], // Avoid Twig conflicts
data() {
return filelist // Store our uploaded files
},
methods: {
onChange() {
this.filelist = [...this.$refs.file.files];
},
remove(i) {
this.filelist = [];
},
dragover(event) {
event.preventDefault();
// Add some visual fluff to show the user can drop its files
if (!event.currentTarget.classList.contains('highlight')) {
event.currentTarget.classList.remove('bg-haze');
event.currentTarget.classList.add('highlight');
}
},
dragleave(event) {
// Clean up
event.currentTarget.classList.add('bg-haze');
event.currentTarget.classList.remove('highlight');
},
drop(event) {
event.preventDefault();
this.$refs.file.files = event.dataTransfer.files;
this.onChange(); // Trigger the onChange event manually
// Clean up
event.currentTarget.classList.add('bg-haze');
event.currentTarget.classList.remove('highlight');
}
},
template: SingleFileUpload
})
new Vue({
el: '#App',
})
This is my .vue file
<template>
<div class="file-upload__wrapper">
<div
class="file-upload__drop-area"
#dragover="dragover"
#dragleave="dragleave"
#drop="drop"
>
<input
type="file"
name="file-upload-bank"
id="file-upload-bank"
class="d-none"
#change="onChange"
ref="file"
accept=".pdf,.jpg,.jpeg,.png"
/>
<ul v-if="this.filelist.length" v-cloak>
<li v-for="file in filelist" v-bind:key="file.id">
<span>Bestand: ${ file.name }</span
><button
type="button"
#click="remove(filelist.indexOf(file))"
title="Verwijder bestand"
>
<i class="icon-cross"></i>
</button>
</li>
</ul>
<label for="file-upload-bank" class="block cursor-pointer" v-else>
<div>Kies een bestand of drag and drop het bestand</div>
</label>
</div>
</div>
</template>
In my page I use to place it.
Unfortunately, I get this error (which I get for all methods I use in the template).
vue.common.dev.js:630 [Vue warn]: Property or method "dragover" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <Test> at assets/scripts/vue-components/forms/single-file-upload.vue
<Root>
Who knows what I do wrong and how to fix it?

Props being mutated in VueJS

I am new to vue and I am getting this error, I am not sure if I am passing the props right and executing it well in the other component. I will explain in details what I am trying to acheive.
I am hiding a component on click on this page and showing another element on click interchangeably here.
I have read a couple of solutions but I do not understand how I'm exactly suppose to fix it
<div v-if="hidden" class="orderSummary">
<div class="orderSummary__container">
<h2 class="orderSummary__header">Order Summary</h2>
<button #click="showForm()" class="total__button">Continue</button>
<PaymentForm v-if="!hidden" :hidden="hiddenMode" />
</div>
</div>
methods: {
showForm() {
if (this.subTotal > 1) {
this.hidden = false;
}
}
},
now in the Payment Form component I need to hide the component and make the other appear also, i want to do this by passing props.
This is my code
<div class="payForm">
<div #click="hideForm()" class="PayForm__icon">
<backIcon class="icon" />
<span class="PayForm__icon-text">Go back</span>
</div>
</div>
props: ["base_amount", "value_added_tax", "hiddenMode"],
methods: {
submit() {
const data = {
name: this.name,
};
},
hideForm() {
this.hiddenMode = true;
}
},
I'm getting the error below, what do I do
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: "hiddenMode"
Don't make hiddenMode a prop; set it to state through data:
//parent
props: ["base_amount", "value_added_tax"],
data() {
return {
hiddenMode: false
}
},
...
edit:
You should also move hideForm() to the parent component and instead bind to PaymentForm's onlick:
#click="$emit('clicked')"
then in the parent component bind hideForm to the clicked emit:
<PaymentForm v-if="!hidden" :hidden="hiddenMode" #clicked="hideForm"/>
Note: the $emit doesn't have to be called "clicked" you can name it anything
First, there is a logical problem here. If hidden is false then the first DIV and children including PaymentForm are not existing.
If hidden is true the PaymentForm not showing too because you have a <PaymentForm v-if="!hidden"
Second, your PaymentForm has a hiddenMode prop and you don't set it in the parent vue. You should have :hiddenMode="hidden" and not :hidden="hiddenMode"
For you hideForm function use $emit
this.$emit('update:hiddenMode', true);
Use the .sync modifier. This way the child component does not modify the property directly. So the parent would be
<PaymentForm v-if="!hidden" :hidden-mode.sync="hiddenMode" />
and the child would be
hideForm() {
this.$emit('update:hiddenMode', true);
}

Props are somehow getting mixed between components

I'm passing two different arrays to the same component but two different instances of it. Then inside those instances I do a v-for and send a single item of array to another component using props. The problem is that when I inspect the Vue tools for the last component, I see that the prop is good but when I try to assign it to data it returns the prop from previous array(the one that was sent to another component).
Laravel:
<co-notifications class-name="writable" nots="{{ $notifications[0] }}"></co-notifications>
<co-notifications class-name="writable extended" nots="{{ $notifications[1] }}"></co-notifications>
CoNotifications:
<template>
<div>
<div v-for="notification in notifications">
<co-notification-item :class-name="className" :not="notification"></co-notification-item>
</div>
</div>
</template>
<script>
import notificationComponent from './NotificationComponent.vue';
export default {
props: ['className', 'nots'],
components: {
'co-notification-item': notificationComponent
},
// data() {
// return {
// notifications: JSON.parse(this.nots),
// }
// },
computed: {
notifications(){
return JSON.parse(this.nots)
}
},
}
</script>
CoNotificationItem
<template>
<div :class="['tableItem',className]">
<div class="textareaWrapper">
<input type="text" class="form-control" placeholder="Title" v-model="notification.title" v-if="notification.type === 'main'">
<textarea class="form-control" rows="6" placeholder="Some text..."
v-model="notification.text"></textarea>
</div>
<div class="buttonWrapper">
<button type="button" class="btn btn-success" #click="updateNotification"><i
class="fe fe-check mr-2"></i>Save
</button>
<button type="button" class="btn btn-danger" #click="deleteNotification"><i
class="fe fe-check mr-2"></i>Delete
</button>
</div>
</div>
</template>
<script>
import notificationComponent from './NotificationComponent.vue';
export default {
props: ['className', 'not'],
components:{
'co-notification-item': notificationComponent
},
data(){
return {
notification: this.not
}
},
methods: {
updateNotification(){
this.notification.text = "test";
},
deleteNotification(){
}
}
}
</script>
As for the data in arrays, I have 2 in the arr(0) and 2 in arr(1).
When I open Vue tools on the FIRST notifications I see this (THIS IS GOOD)
However, when I open other notifications that read from arr(1) I see this (this is obviously not how it's supposed to be)
As you can see I used computed for the CoNotification but if I remove it and use only data() both nots recieve the same array, but if I use computed it is okay. However, I can't use computed in CoNotificationItem beacuse I need to have it in data() so I can bind it with v-model.
So, my question is, how to make notification on the CoNotificationItem be the same as not (variable) but be accessible in data() so I can put v-model to it - why am I getting mixed values?
Note: I also tried with computed and watch and created/mounted.
I've been stuck at this problem for half the day and I searched my as* off both in official docs and tutorials/questions on stackoverflow and whatnot.
Some searches that I tried :
Vue.js passing props to data
Passing data from Props to data in vue.js
https://forum.vuejs.org/t/update-data-when-prop-changes-data-derived-from-prop/1517
Adding unique key property to each v-for item will solve the problem
<div v-for="notification in notifications" :key="someUniqueKey">
<co-notification-item :class-name="className" :not="notification"></co-notification-item>
</div>
I can not explain it clearly. But as i understand, Vue tracking elements by key property. Give these elements unique key will tell Vue that they are different elements. So it will prevent Vue re-using prop and data.
If anyone can explain more detail and refer document please add comment or new answer. I will delete my answer.

Vue.js multiple instances of same component issue

I have created a vue component for selecting photos. When the user clicks any photo the id of the photo will be assigned to a hidden input field inside the component.
Now I have used this component twice on the same page with different data. The problem is when I click on the photo of one component the value of the input field of both the components gets updated. I am using vue.js version 2.1.10
Here is the simplified version of my component.
<div>
<input type="hidden" :name="inputName" :value="currentSelectedPhoto.id">
<div v-for="photo in photos">
<div v-on:click="updateSelectedPhoto(photo)">
<img :src="photo.photo_url" />
</div>
</div>
</div>
The Component
export default {
props: {
...
},
methods: {
getPhotos(){
...
},
updateSelectedPhoto(photo){
this.currentSelectedPhoto = photo;
}
}
}
This is how I am using it in html
<div>
<div>
Profile Photo
<photo-selector
photos="{{ $photos }}"
input-name="profile_photo_id"
>
</photo-selector>
</div>
<div class="col-sm-4">
Cover Photo
<photo-selector
photos="{{ $otherPhotos }}"
input-name="cover_photo_id"
>
</photo-selector>
</div>
</div>
Based on your codepen sample, it's because you are sharing the state object between the two:
const initalState = {
selectedPhoto: null
};
const PhotoSelector = Vue.extend({
data: () => {
return initalState
},
Vue mutates the initial state object (by wrapping it in reactive getters etc), so you need to have data() return a fresh state object for the instance to use:
data: () => {
return {
selectedPhoto: null
};
},

Categories