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
Related
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 have a global service widgetService which holds data for a number of widgets, each identified by a widgetID. Each widget's data can change at any time. I want to display a widget with a React component, say WidgetReactComponent.
The react component shall take a widget ID as property and get the information to display from the widget service. A widget's data can be queried from the widget service with the method getWidgetData(widgetID). And in order to be able to publish data changes, it also offers two methods: addListenerForWidget(widgetID, listener) and removeListenerForWidget(widgetID, listener).
When assuming that the property is set once and never changed, this can be achieved like this, following React's recommendations:
class WidgetReactComponent extends Component {
constructor() {
super();
this.state = {
data: widgetService.getWidgetData(this.props.widgetID)
};
this._onDataChange = this._onDataChange.bind(this);
}
_onDataChange(newData) {
this.setState({data: newData});
}
componentDidMount() {
// React documentation: "This method is a good place to set up any subscriptions."
widgetService.addListenerForWidget(this.props.widgetID, this._onDataChange);
}
componentWillUnmount() {
// React documentation: "Perform any necessary cleanup in this method, such as [...] cleaning up any subscriptions that were created in componentDidMount()."
widgetService.removeListenerForWidget(this.props.widgetID, this._onDataChange);
}
render() {
return <div className="Widget">{this.state.data.stuff}</div>;
}
}
The component may then be used like this:
<ReactWidgetComponent widgetID={17} />
However, the widgetID property may change at any time, and the component has to handle this in order to function properly under all circumstances. By react's recommendation, this should be handled by setting the state based on properties using the static getDerivedStateFromProps function. But since it is static, I do not have access to the component and cannot change the listeners accordingly.
One way to work around this would be to store the widgetID in the state, and then use the lifecycle method componentDidUpdate to detect the change, like this:
constructor() {
super();
this._onDataChange = this._onDataChange.bind(this);
}
static getDerivedStateFromProps(nextProps) {
return {
widgetID: nextProps.widgetID,
data: widgetService.getWidgetData(nextProps.widgetID)
};
}
componentDidUpdate(prevProps, prevState) {
if (prevState.widgetID !== this.state.widgetID) {
widgetService.removeListenerForWidget(prevState.widgetID, this._onDataChange);
widgetService.addListenerForWidget(this.state.widgetID, this._onDataChange);
}
}
However, componentDidUpdate won't be called when shouldComponentUpdate returns false. This doesn't feel like a safe way of doing this. Also I believe the listeners will be wrong for the entire timespan between the property change and the update's completion. How could I safely implement this?
You don't need to store widgetID in state, you can compare prevProps with this.props:
componentDidUpdate(prevProps, prevState) {
if (prevProps.widgetID !== this.props.widgetID) {
widgetService.removeListenerForWidget(prevProps.widgetID, this._onDataChange);
widgetService.addListenerForWidget(this.props.widgetID, this._onDataChange);
}
}
You will also need to add the listener in componentDidMount as componentDidUpdate is not called on first rendering:
componentDidMount() {
widgetService.addListenerForWidget(this.props.widgetID, this._onDataChange);
}
Regarding your concerns:
componentDidUpdate won't be called when shouldComponentUpdate returns false
From the docs:
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props.
So if you decided to not update the component when this.props.widgetID changes, then you are violating the assumption/purpose of shouldComponentUpdate and should not expect your widget listener to be updated.
A lot of things will not work as intended if you misuse shouldComponentUpdate anyway (eg. component not updated to reflect new data), so relying on an API being used correctly as per official docs is a necessity to achieve simplicity, rather than something to be avoided.
the listeners will be wrong for the entire timespan between the property change and the update's completion
By this logic, when you update some displayed data in an event handler, you can also claim that the data displayed is wrong for the entire timespan between the event and the re-rendering. You can even claim that your text editor is displaying the wrong data between the time you press a keyboard key and rendering of the key on the screen.
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.
All props form a one-way-down binding between the child property and
the parent one: when the parent property updates, it will flow down to
the child, but not the other way around. This prevents child
components from accidentally mutating the parent’s state, which can
make your app’s data flow harder to reason about. In addition, every
time the parent component is updated, all props in the child component
will be refreshed with the latest value. - One-Way Data Flow
The Vue2 Component Docs suggests doing the following to use props as an initial value:
// via https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
So in my code I mimicked those instructions here.
However data() in Note.vue isn't being updated even though the prop value is received according to vue-devtools.
Haven't had success setting the values with the mounted or created lifescyle methods.
When I use static data, this seems to work fine, how can I ensure the child component reacts to receiving props when it comes from a remote source?
When you are passing initialNote as prop for initial value, but I see initialNote is being populated asynchronously in getNote method, so it will not be present initially when the component will be mounted. It will be populated after some time by the time initialisation would have already happened.
In the example give in vue documentation, initialCounter is static value which will perfect as it will have same value from beginning.
After completing the guide on the Alt site I am confused if Alt uses Reacts state to set properties? In part 3 - using Stores, it says
Instance variables defined anywhere in the store will become the
state.
It then puts a variable in the store:
this.locations = [];
Yet if I go into the main React component and log the state, after locations is given data of course, I get undefined except on the props?
loggState() {
console.log(this.state); // undefined
console.log(this.locations); // undefined
console.log(this.props.locations); // [object][object]..
}
Can anyone explain the relationship between states and props when using Alt?
In Alt—and indeed most Flux implementations—the store is a totally different part of your application to your components.
A component subscribes to changes in a store, then uses the changed data to update its local state, causing it to re-render.
We derive the initial state of components that use the LocationStore from whatever the store's state is when we instantiate the component.
getInitialState() {
return LocationStore.getState();
},
Then we set up a subscription, listening for changes to the store.
componentDidMount() {
LocationStore.listen(this.onChange);
},
And finally, we use the subscription handler to apply these changes to the component's local state. The way you decide to apply each update is totally up to you.
onChange(state) {
this.setState(state);
},
Each time we call this.setState with a new state, the component will re-render.
You could also use a higher-order component to subscribe to the store, then covert the store's state to props and pass them down to a wrapped component, instead. In which case, your component wouldn't need to be stateful at all.