Vuex - How to make multilevel array reactive? - vue.js

How to make multilevel array reactive?
I have something like this.
state.scenes[state.sceneIndex].contents[payload.contentIndex]
whereas Scene and Content have their own components. I was able to update the data in Vuex however, the changes are not reflecting in the UI. I already tried using
Vue.set(state.scenes[state.sceneIndex].contents, payload.contentIndex, payload.value)
but didn't work.
Will really appreciate your help.

I found the answer.
It was the ...MapState that causes this issue.
I just created another computed that returns the value from mapState.
computed: {
...mapState('SomeModule', ['SomeState']),
someName() {
return this.someState
}
}

Related

Vue mutate prop correctly

I'm trying to create a simple component whose focus is to display an element in an array, but I'm having issues with Vue's philosophy.
As you may know, if a mutation on a prop is triggered, Vue goes crazy because it doesn't want you to update the value of a prop. You should probably use a store, or emit an event.
The issue is: that since I'm adding functionalities to my codebase (for instance the possibility to start again when I reach the last element of the array), it would be wrong to have an upper component be responsible for this management, as it would be wrong to ask an upper component to change their variable, given that my component is supposed to manage the array, so an emit would be a bad solution.
In the same way, given that I'm making a generic component that can be used multiple times on a page, it would be incorrect to bind it to a store.
EDIT: the reason why the prop needs to be updated is that the component is basically acting as a <select>
Am I missing an obvious way to set this up?
To give an example of my end goal, I'm aiming for a component looking like the one in the picture below, and I think a 2 way bind like in v-model would be more appropriate than having to set an #change just to say to update the value of the passed prop.
If you have a prop the correct way to update the value is with a sync, as in the following example
Parent:
<my-component :title.sync="myTitle"></my-component>
Child:
this.$emit("update:title", this.newValue)
Here is a very good article talking about the sync method.
By the other hand you can alter a Vuex state variable by calling a Vuex mutation when you change the value:
computed: {
title: {
// getter
get() {
return this.$store.state.title
},
// setter
set(newValue) {
this.setTitle(newValue) // Requires mutation import, see the methods section.
// Or without import:
this.$store.commit('setTitle', newValue);
}
}
},
methods: {
...mapMutations("global", ["setTitle"]) // It is important to import the mutation called in the computed section
}
In this StackOverflow question they talk about changing state from computed hook in Vue. I hope it works for you.

Vue Chart JS and options reactivity

I'm trying to recreate the doughnut to pie change in behavior as seen here:
https://www.chartjs.org/samples/latest/scriptable/pie.html
I'm using VueJS version of Chart JS and after recreating this it seems to not be reactive at all.
Here is the method that I use to change the chart to the other one:
togglePieDoughnut() {
this.options.cutoutPercentage = 50;
}
As you can see it does not work as intended, even tough I used reactiveprop mixin.
EDIT: To be precise I want to recreate the chart update behaviour as seen in the example on chartjs.org website. I do not want to rerender the chart, rather update it so the transition remains smooth.
Seems like the issue is the template isn't reacting to the data changes. Best way to force template re-render is to bind a key, for our example, we are changing this value, the template will update when its changed:
:key="options.cutoutPercentage"
Codepen example:
https://codesandbox.io/s/vue-chartjs-demo-t8vxu?file=/src/App.vue
What if we add a watch to the options variable and rerender the chart when it happens?
watch: {
options: function() {
this._chart.destroy();
this.renderChart(this.donut, this.options);
}
}
When you look at the code of Piechart.vue, it seems that it only render one time on mounted. Thats why when changing the options, it not gonna reflected in the chart because there is no function to rerender.
The only way is you have to remove the old pie chart and create a new one when options changed. There's a lot of way to do the force re-render, but still the cleanest way is as procoib said, attach a key to it.
When using object as props and update one property in it, the reactivity system will not trigger the change, because the object is the same and only one property updated. Thus, the child component will not get the updated value.
What can you do is to recreated the object with the updated property. See below code:
this.options = Object.assign({}, this.options, { cutoutPercentage: 50 });
And in the child component, use watcher re-render the chart
watch: {
options(newVal) {
}
}

how blur or focus vuejs component based on a vuex state

I've got a little problem with vuejs.
My state is basically something like this.
state: ()=>({
activeSide : "from",
}),
I want a component to be focused or blurred based on whether activeSide has the value activeSide set to "from" or not
my idea at the moment, doesn't seem to be very elegant, I basically created a computed property in my component,
focusSide(){
console.log("changed active side")
this.$store.state.activeSide
}
and then I set up a watch to see if that property changes.
watch:{
focusSide : function(newV,_){
console.log("changing focus")
newV=="from" ? this.$refs.fromArea.$el.focus() : this.$refs.fromArea.blur()
}
},
the problems here is that apart from the fact that the solution doesn't look elegant the watch doesn't work either, I've seen that focusSide is changing its value correctly (or at least the body of the method is executed), but the watcher is not executed, I have the feeling that since focusSide state is never used in my template, vuejs thinks that it's not necessary to react and change values, something like reactive frameworks where if the value is not observated then don't change (maybe I'm wrong)
what would be the ideal way for achieve this???
thank you
You need return value of computed properties focusSide, otherwise it will always return undefined
focusSide () {
console.log("changed active side")
return this.$store.state.activeSide
}

Difference in Vue between data() and adding data in created()

Is there a difference between the following? I've seen examples doing both and am unsure why you would choose one over the other.
Vue.component('test', {
data() {
return { myDataA: 10 };
}
//vs
created() {
this.myDataB = 10;
}
}
Variables set in created() on this will not be reactive. In order for them to be reactive, you must define them in the object returned by data().
Example (note how the text does not change in the output):
https://jsfiddle.net/oyf4quyL/
in a component, there are three places where you can define you data:
data properties
computed properties
props
the created property is lifecycle hook in Vue. what this means, is that the Vue will run this function when the component is created. there are also other lifecycle hooks in Vue you can use, like mounted or beforeMount or beforeCreate and etc.
now with this in mind, let's answer your question.
when you define myDataA in data property, Vue will automatically create some "watchers" for this data property, so anytime that you set a new value to myDataA, anywhere that is using it, will be called again. but when you define a property directly on Vue instance (this), you will lose this "watchers" feature. (which by the way is just some getters and setters!)
so as i said, the best way and the correct way to define a data property is on any of the three places that i mentioned, based on your need. (because each of them has different use-cases that the others).

How can I watch synchronously a state change in vuex?

I am using an opensource vuejs + vuex project and this is the source https://github.com/misterGF/CoPilot/tree/master/src/components
I am currently having problems knowing how to trigger an event from one components to another.
I can use this.$state.store.commit("foo", "bar") to store information in vuex, but when two seperate have two difference export default {} I don't know how I can make the app aware whenever "foo" is for exampled changed to "baz" ... unless I refresh/reload the app, there is no way for me to know the changes
Use this.$store.watch on your component. Created() is a good place to put it. You pass it the state to watch and then specify the callback for when it changes. The Vuex docs do not give good examples. Find more information on this Vuejs Forum page. Store watch functions the same as the vm.$watch, so you can read more about that here in the Vue docs.
this.$store.watch(
(state)=>{
return this.$store.state.VALUE_TO_WATCH // could also put a Getter here
},
(newValue, oldValue)=>{
//something changed do something
console.log(oldValue)
console.log(newValue)
},
//Optional Deep if you need it
{
deep:true
}
)
Your question is not entirely clear so I am going to make some assumptions here.
If you simply want your app to know when a store value has changed you can use a watcher to watch a computed property that is directly linked to a store getter:
So you would set a watcher on something like this:
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
},
watch:{
doneTodosCount(value) {
console.log(`My store value for 'doneTodosCount' changed to ${value}`);
}
}
If you want your commit to behave differently depending on what the current value of your foo property is set to, then you can simply check for this in your commit function.
Let me know if you have some more questions.