Vue test watcher change but code inside not response - vue.js

I'm stuck in this situation where I have a getters from Vuex Store, and whenever that getter change (new value update), the local state data(the 'list') should be reassign .
This is my component, which has 'list' in data
And here is my test Successfully change the getSkills to the getSkillsMock, but there is no response from list, list is still an []

You need to set deep to true when watching an array or object so that
Vue knows that it should watch the nested data for changes.
watch: {
getSkills: {
handler () {
this.list = this.getSkills.map(items => {
return { value: items.id, label: items.name }
})
},
deep: true
}
}

Related

Vuex: Add Dynamic Property to Object without triggering Watchers on existing Properties

I have a Vuex store with an object:
state: {
contents: {},
}
where I dynamically store contents by key:
mutations: {
updateContent: (state, { id, a, b }) => {
Vue.set(state.contents, id, { a, b });
},
}
and get them using:
getters: {
content: (state) => (id) => {
if (id in state.contents) return state.contents[id];
return [];
}
},
Let's say I have a component like this:
export default {
props: ["id"],
computed: {
myContent() {
return this.$store.getters.content(this.id)
}
},
// ...
}
How can I add dynamic properties using the mutation without triggering changes in components watching unchanged, already existant properties of state.contents?
Also see this fiddle.
If you want to watch inner property of objects, you can use deep watchers.
In your situation, i am assuming you're setting properly yor getters, setter and update methods. You should add this to your watchers:
// ...
watch:{
id: {
deep: true,
handler(newVal, oldVal){
console.log("New value and old value: ", newVal, oldVal")
// ... make your own logic here
}
}
}
Let me explain little bit more above code, when we want to watch inner property of any object in Vue, we should use deep and handler function to manipulate in every change. (remember that, handler is not a random name, it's reserved keyword)
I'm trying to figure it out by checking the fiddle: https://jsfiddle.net/ericskaliks/Ln0uws9m/15/ , and I have a possible reason to this behavior.
Getters and Computed properties are updated or reached when the observed objects or properties inside them are changed. In this case the content getter is "watching" the state.contents store property, so each time store.contents is updated, the content getter is called, then the computed property myContent() updates its value and increase the updated data property with this.update++.
So the components will always be updated if the state.contents property is updated, i.e. adding a new unrelated property.
One could use a global event bus instead of a vuex getter:
const eventBus = new Vue();
components can subscribe to the element they need:
watch: {
id: {
handler(n, o) {
if (o) eventBus.$off(o + "", this.onChange);
eventBus.$on(n + "", this.onChange);
this.$store.dispatch("request", { id: n });
},
immediate: true
}
},
and changes of which the components have to be notified are dispatched using an action:
actions: {
request(_, { id }) {
eventBus.$emit(id + "", this.getters.content(id));
},
updateContent({ commit }, { id, a, b }) {
commit("updateContent", { id, a, b });
eventBus.$emit(id + "", { a, b });
}
}
That way one can precisely control when which updates are fired.
fiddle.
It seems like Vue(x) can't do this out-of-the-box. But by adding an empty Observer (yes, this is a hack) you can make Vue-components temporarily nonreactive (based on this blogpost).
Basically - we have an Observer on the object itself and an Observer on each of the properties. We will destroy the Observer on the object. When doing so, we have to make sure, that when the getter is first called by a component it returns a reactive value rather then {}, because the getter can't observe adding a new property to the object anymore. Therefore, we add a touch-mutation initializing the object. This function needs the original Observer of the object to create Observers on its properties, causing one, but only one, unnecessary update of the component:
mutations: {
updateContent: (state, { id, a, b }) => {
Vue.set(state.contents, id, { a, b });
},
touch: (state, { id }) => {
if(id in state.contents) return
if (myObserver === null)
myObserver = state.contents.__ob__
state.contents.__ob__ = myObserver
Vue.set(state.contents, id, {});
state.contents.__ob__ = new Observer({});
}
},
The constructor can be obtained using:
const Observer = (new Vue()).$data.__ob__.constructor;
Our component has to call touch whenever the id changes:
props: ["id"],
watch: {
i: {
immediate: true,
handler() {
this.$store.commit("touch", { id: this.id })
}
}
},
computed: {
myContent() {
return this.$store.getters.content(this.id)
}
},
As you can see in this fiddle, adding new properties to the object doesn't trigger unnecessary updates anymore.

Vuex State watch over Compute not working

I have empty state in the beginning like so
new Vuex.Store({
state: {
comboBoxNewValues: {}
},
Over time, there are mutations which changes the state like so
this.$store.commit('addComboBoxValues', { input: 'foo', value: ['value': 1, 'name': 'bar']});
Using the following mutation code
mutations: {
addComboBoxValues(state, _value) {
state.comboBoxNewValues[_value.input] = _value['value'];
},
}
It works perfectly and mutations also changes the state, Now I want to perform some action on change of the state so in my component I added a computed property like so
computed: {
getComboBoxNewValues() {
return this.$store.state.comboBoxNewValues;
}
}
When I debug using vue-dev-tools, the components computed property has data. Whenever the data changes using mutation in the vuex, that data is reflected in the computed property as well.
Now I add a watcher to it like so
watch: {
getComboBoxNewValues:{
handler: function(to, from) {
console.log("reload");
},
deep: true
}
}
The problem is that the never never gets called, no matter how many times the data has changed in the computed property. Please advice on what I am missing.

When passing data from parent component to child component via props, the data appears to be undefined in the mounted hook of the child component

In my parent component:
<UsersList :current-room="current_room" />
In the child component:
export default {
props: {
currentRoom: Object
},
data () {
return {
users: []
}
},
mounted () {
this.$nextTick( async () => {
console.log(this.currentRoom) // this, weirdly, has the data I expect, and id is set to 1
let url = `${process.env.VUE_APP_API_URL}/chat_room/${this.currentRoom.id}/users`
console.log(url) // the result: /api/chat_room/undefined/users
let response = await this.axios.get(url)
this.users = response.data
})
},
}
When I look at the page using vue-devtools, I can see the data appears:
I've run into this issue in the past – as have many others. For whatever reason, you can't rely on props being available in the component's mounted handler. I think it has to do with the point at which mounted() is called within Vue's lifecycle.
I solved my problem by watching the prop and moving my logic from mounted to the watch handler. In your case, you could watch the currentRoom property, and make your api call in the handler:
export default {
props: {
currentRoom: Object
},
data() {
return {
users: []
}
},
watch: {
currentRoom(room) {
this.$nextTick(async() => {
let url = `${process.env.VUE_APP_API_URL}/chat_room/${room.id}/users`
let response = await this.axios.get(url)
this.users = response.data
})
}
},
}
I don't think you really need to use $nextTick() here, but I left it as you had it. You could try taking that out to simplify the code.
By the way, the reason console.log(this.currentRoom); shows you the room ID is because when you pass an object to console.log(), it binds to that object until it is read. So even though the room ID is not available when console.log() is called, it becomes available before you see the result in the console.

Computed property react to localstorage change

I'm saving an array into local storage
and adding/removing from the array like.
I want the count of the array to update in the component as and when new items get added to the array in localstorage
I am using a computed property:
numOfCodes: {
// getter
get: function() {
let storageItems = localStorage.getItem("items");
if (storageItems) {
var items = JSON.parse(storageItems);
return items.length;
}
return 0;
}
}
The count is not changing as expected. it remains the same.
I have tried using vuex, but still have the issue. the goal is having the value react to the localstorage change
I think a solution to this would be to use vuex, I've mocked up an example below:
On your component:
computed: {
...mapGetters({
itemsCount: 'mockLocalStorage/itemsCount'
})
},
created() {
this.setItems(...);
},
methods: {
...mapActions({
setItems: 'mockLocalStorage/setItems'
})
}
In vuex:
state = {
items: []
};
getters = {
itemsCount: state => state.items.length
};
actions: {
setItems({ commit }, items) {
localStorage.setItem('items', items);
commit('setItems', items);
}
};
this.itemsCount would then be reactive in your component, and you could create a few more actions to add and remove individual items.
The localStorage does not share the reactivity system of Vue. This whole process is handled by Vue itself. See also here. I think you should be able to manually trigger a re-render by forcing Vue to update all of its components using forceUpdate. However, keep in mind that you would have to trigger the re-render whenever you update the localStorage or whenever you expect it to be updated.
Use a watcher.
props: ['storageItems', 'itemsLength'],
watch: {
storageItems: function(newVal, oldVal) {
this.storageItems = newVal
this.itemsLength = newVal.length
}
}

Computed property needs to wait for async data

export default {
data() {
return {
projects: []
}
},
mounted() {
axios.get('...')
.then(({ data } => this.projects = data)
},
computed: {
personalProjects() {
return this.projects.filter(...)
},
commercialProjects() {
return this.projects.filter(...)
}
}
}
The computed properties need to wait for projects to be fetched from the server before setting the data. What's the proper way to do this?
I tried this:
watch: {
projects() {
this.personalProjects = this.projects.filter(project => project.type === 'personal')
this.commercialProjects = this.projects.filter(project => project.type === 'commercial')
}
},
but I got an error message: Computed property "personalProjects" was assigned to but it has no setter.
Should I set personalProjects and commercialProjects in data() instead?
What you are currently doing is the correct approach. Computed properties are reactive, derived, properties. They will reactively update whenever projects is updated by the data request.
In essence, your component's logic starts off with no projects, [] and if anyone asks for personal or commercial projects they are given the correct result: there are none of either, [].
However, whenever the component is mounted, it starts the process of loading the actual projects and whenever it's done, the whole dependency graph of projects will be reactively updated meaning personalProjects will be provide the correct result.