I am writing a component which receives data via a prop, modifies that data, and then $emit it back to the parent (upon its change, so it is also in watched).
Can a prop be reactive? Or, alternatively, how to assign a prop to a reactive property?
I tried to avoid this question by:
having props: ["receiveddata"]
defining a reactive property in data (say, hello: '')
in mounted() ensure that this.hello = this.receiveddata
I expected at that point this.hello to carry the content brought in by the receiveddata prop, and from that point on work on this.hello (that is: modify it, watch it and $emit it when relevant)
However, when looking at the values in DevTools I see that
this.hello is undefined
this.receiveddata correctly holds the data passed to the component
I conclude that, when in mounted(), the props values are not yet known (thus the undefined of this.receiveddata), this undefined is assigned to this.hello, and then later this.receiveddata gets populated (but it's too late for me).
In other words, I plugged my this.hello = this.receiveddata in the wrong place, but I do not really know where it could go elsewhere (I also tried to change mounted() to created())
I might not be understanding 100% but I think this is what you're after.
I typically use a computed property with a getter and a setter in this case.
props: {
someProp: String,
},
computed: {
somePropLocal: {
get () {
return this.someProp
},
set (value) {
this.$emit('input', value)
},
},
}
This way you can do this freely and the proper value will be emitted.
this.somePropLocal = 'Whatever'
Also, somePropLocal will be updated if the parent changes it directly as well.
Related
I'm wondering if I'm doing this the best way possible; I've built a component which accepts a list of items as a prop, but then I need to mutate that list internally in the component, so I followed the VueJs docs to provide the prop value as a data property's initial value, and then using the data property when I need to make any changes to the data.
export default {
props: {
items: {
type: Array,
default: () => {
return []
}
}
},
data() {
return {
itemList: this.items
}
}
}
I noticed however that this causes the itemList property to stick to the initial value, and won't update if the parent component provides a new items value to this component, this makes sense as the component was already rendered and thus it doesn't need to reevaluate all it's data properties; so to fix this I added a watcher to watch for any changes on items:
watch: {
items() {
this.itemList = this.items;
}
}
I found this other question which had a similar issue and the accepted answer did just what I just did here; however I'd like to know if this is an expected and reliable method of fixing this issue or if there are other ways to do this?
The data property uses the prop just for initialization. It won't react to prop value changes after that.
A computed property will, though. It's another way to do it, but since you're not actually doing any "computing" on the prop value, a watcher with a function that changes the data property seems more appropriate to me.
A regular watcher is shallow, though. Your suggestion shouldn't work if the parent element just mutated the array by adding or removing elements. It should only react to a reassignment of the array (I guess that's what your parent element did... replace the prop value entirely).
If you need the watcher to react to mutations to the prop as well, then you might want to create a deep watcher:
watch: {
items: {
handler(newValue) {
this.itemList = JSON.parse(JSON.stringify(newValue)); //deep array copy to prevent array mutations made by the child component from affecting the parent's array. Keep in mind this deep-copy technique using JSON has caveats.
},
deep: true
}
}
But that can become quite expensive if your array prop grows big.
Also, it seems to me you intend for the state of your component ($data.itemList) to be changed both by the component itself and by its parent *at the same time*. This can get troublesome (racing conditions and whatnot), so be careful.
I have a Prop in my component that is a User object, I then have this function:
onChange: function(event){
this.$v.$touch();
if (!this.$v.$invalid) {
this.$axios.put('update',{
code:this.user.code,
col:event.target.name,
val:event.target.value
}).then(response => {
this.user[event.target.name]=event.target.value
});
}
}
I can see in the Vue console debugger that the correct attribute has been updated, this attribute exists when the component is created but the template where I reference it does not refresh:
<p>Hi {{user.nickname}}, you can edit your details here:</p>
This is all within the same component so I'm not navigating to child or parent. I'm sure props have been reactive elsewhere in my code?
Ok, it seems this is intended behaviour. According to the documentation
https://v2.vuejs.org/v2/guide/components-props.html in the scenario that I have it should be handled as:
The prop is used to pass in an initial value; the child component wants to use it as a local data property afterwards. In this case,
it’s best to define a local data property that uses the prop as its
initial value:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
Usually components should be reactive to Props, though i have had experiences where it was non-reactive so i added the prop to a watcher and put the functional call there.
props: ["myProp"],
watch:{
myProp(){
// ... your functions here
}
}
I am having an issue with watchers not being triggered upon data change in my component. The property in the data is reactive, as it has been set upon component creation and not later on.
Here is the piece of code, in which the issue is:
https://codesandbox.io/s/nlpvz0y6m
To explain in more detail, the status property gets its status from the parent, which intern gets it from a Vuex state object, it is being passed to the component successfully, as I am able to log it and change it.
However, when I setup a watcher, to execute a function upon a change in it's value, it simply doesn't trigger. Regardless how I make the change - whether with an internal method of the component or an event.
What I need is for the watcher to trigger upon change of the status property, but am not certain why it does not reflect it at all.
The structure is as it follows: BottomBar is the parent, a bool value is passed as property to Spin.vue as a prop and then the prop is assigned to a data property on the child component.
The bool value itself, comes from index.js, where the Vuex instance is.
In the console, it is showing the following two errors
[vuex] unknown getter: isSpinning
[vuex] unknown mutation type: spinIt
The issue seems to be how the store is set up. Try this.
export const store = new Vuex.Store({
state: {
controls: {
spin: false
}
},
getters: {
isSpinning: state => {
return state.controls.spin;
}
},
mutations: {
spinIt(state) {
return (state.controls.spin = !state.controls.spin);
}
}
});
You had your mutations and getters sat inside your state. I have moved them outside, and updated the references inside to make the code work as expected.
https://codesandbox.io/s/8xyxmvr8jj
could you please tell me how to use watch function in vue js .I tried to used but I got this error.
vue.js:485 [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: "m"
found in
---> <AddTodo>
<Root>
https://plnkr.co/edit/hVQKk3Wl9DF3aNx0hs88?p=preview
I created different components and watch properties in the main component
var AddTODO = Vue.extend({
template: '#add-todo',
props: ['m'],
data: function () {
return {
message: ''
}
},
methods: {
addTodo: function () {
console.log(this.message)
console.log(this.m);
this.m =this.message;
},
},
});
When I try to add item I am getting this error.
Step to reproduce this bug
Type anything on input field and click on Add button
this.m =this.message;
this line is the issue,
It's recommended that you don't modify prop directly...
instead create a data property and then modify it.
It shows warning because you're modifying the prop item, prop value will be overwritten whenever the parent component re-renders.
The component's props are automatically updated in the component as soon as you change their value outside of it.
For this reason, trying to change the value of a property from inside your component is a bad idea: you should use the props as read-only.
If you want to use a prop as the initial value of some of your component's data you can simply declare it this way:
data: function () {
return {
changeable: this.receivedProp;
}
},
That being said, if you are trying to change the value of a prop from inside a component to be able to use your reassigned prop outside of it, you are doing it the wrong way. The way you should handle this is by using Vue's custom events.
Remember, as Vue's documentation states:
In Vue, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events.
I'm using AJAX to get some JSON, and then I want to display the value. If I log out he object that contains the value I want to display, I can see the key and the value. However, when I try to access the value directly, I get undefined.
Here is the component that I am stuck on:
var WeatherCard = React.createClass({
getInitialState: function () {
return {};
},
componentDidMount: function() {
var comp = this;
$.get("http://api.openweathermap.org/data/2.5/weather?zip=" + this.props.zipcode + ",us", function(data) {
comp.setState(data);
});
},
render: function() {
// I want to get the value # this.state.main.temp
// this works...
console.log(this.state.main);
// this does not work...
// console.log(this.state.main.temp);
// I want "current temp" to display this.state.main.temp
return (
<div className="well">
<h3>{this.state.name}</h3>
<p>Current Temp: {this.state.main} </p>
<p>Zipcode: {this.props.zipcode}</p>
</div>
);
}
});
Here is the whole plunk.
http://plnkr.co/edit/oo0RgYOzvitDiEw5UuS8?p=info
On first pass, this.state is empty which will render this.state.main.temp as undefined. Either you prefill the state with a correct structured object or wrap in if clauses.
For objects that returns null or undefined React will simply skip rendering it without any warnings or errors, however when you have nested structures that return null or undefined normal JavaScript behaviour is employed.
Try setting your initial state using main: [], data: [] rather than an empty object.
I was having the same problems and once I took my AJAX setState and made an empty initial state everything started working fine.
I'm new to React so I can't give you a very good explanation as to why this made a difference. I seem to stumble into all kinds of strange problems.
Hope this helped.