Vue-chartjs : reload chart on state change - vuejs2

I’m trying to make a chart reactive to state change, I have some filters on a sidenav and a component which include chart.js component like this :
<tendance-chart slot="content" class="chart-wrapper" v-if="!loading" :labelsRepartitionTendance="labelsRepartitionTendance" :repartitionTendance="repartitionTendance"></tendance-chart>
That’s works perfecly, but I want to reload this chart when my filters change, how can I do that ?

When your filter changes before the function execution change loading to true and at the end change loading to false. This however is not good a good practice, basically your chart should listen for its data and re render itself every time something is changed.

vue-chartjs ships with two mixins the reactiveDataMixin and the reactivePropMixin.
Which are adding a watcher to chartData and re-rendering oder updating the chart, if the data changes. http://vue-chartjs.org/#/home?id=reactive-data

you must use watch and pass a setTimeout() again calling the function that handles the data.
watch: {
dataToObserve() {
setTimeout(() => {
this.functionThatHandlesData();
}, 1000);
}
},

Related

Vue3 child component does not recreating, why?

I have made some sandbox code of my problem here:
https://codesandbox.io/s/clever-zeh-kdff1z
<template>
<div v-if="started">
<HelloWorld :msg="msg" #exit="exit" #remake="remake" />
</div>
<button v-if="!started" #click="started = !started">start</button>
</template>
<script>
import HelloWorldVue from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
HelloWorld: HelloWorldVue,
},
data() {
return {
started: false,
msg: "Hello Vue 3 in CodeSandbox!",
};
},
methods: {
exit() {
this.started = false;
},
remake() {
this.msg = this.msg + 1;
//this code should recreate our child but...
this.exit();
this.started = true;
// setTimeout(() => {
// this.started = true;
// });
},
},
};
</script>
So! We have 2 components parent and child. The idea is simple - we have a flag variable in our parent. We have a v-if statement for this - hide / show an element depend on the flag value "false" or "true". After we toggle the flag - the child component should be recreated. This is the idea. Simple.
In our parent we have a button which will set the flag variable to "true" and our child will be created and will appear on our page.
Ok. Now we have 2 buttons inside our child.
One button is "exit" which is emit an event so the flag variable of parent will set to "false" and the elemint will disappear from our page(It will be destroyed btw). Works as charm. Ok.
The second button "remake". It emit event so the flag variable will be just toggled (off then on). Simple. We set to "false", we set to "true". So the current child should dissapear, and then imediatly will be created new one.
But here we are facing the problem! Ok, current child is still here, there is no any recreation, it just updates current one... So in child I have checked our lifecycle hooks - created and unmounted via console.log function. And the second button dont trigger them. Start->Exit->Start != Start->Remake.
So can anyone please explain me why this is happening? I cant figure it out.
Interesting thing, if you can see there is some asynchronous code commented in my demo. If we set our flag to "true" inside the async function the child will be recreated and we will see the created hook message but it seems like crutch. We also can add a :key to our component and update it to force rerender, but it also seems like a crutch.
Any explanations on this topic how things work would be nice.
Vue re-uses elements and components whenever it can. It will also only rerender once per tick. The length of a 'tick' is not something you should worry yourself about too much, other than that it exists. In your case the this.exit() and this.started = true statements are executed within the same tick. The data stored in this.started is both true in the last tick and the current tick as it does not end the tick in between the statements, and so nothing happens to your component.
In general you should think in states in Vue rather than in lifecycles. Or in other words: What are the different situations this component must be able to handle and how do you switch between those states. Rather than determining what to do in which point in time. Using :key="keyName" is indeed generally a crutch, as is using import { nextTick } from 'vue'; and using that to get some cadence of states to happen, as is using a setTimeout to get some code to execute after the current tick. The nasty part of setTimeout is also that it can execute code on a component that is already destroyed. It can sometimes help with animations though.
In my experience when people try to use lifecycle hooks they would rather have something happen when one of the props change. For example when a prop id on the child component changes you want to load data from the api to populate some fields. To get this to work use an immediate watcher instead:
watch: {
id: {
handler(newId, oldId) {
this.populateFromApi(newId);
},
immediate: true
}
}
Now it will call the watcher on component creation, and call it afterwards when you pass a different id. It will also help you gracefully handle cases where the component is created with a undefined or null value in one of the props you expect. Instead of throwing an error you just render nothing until the prop is valid.

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) {
}
}

Dynamically changing chart types with Vue Chart.js

I'm trying to use Vue Chart.js to implement a chart selector to visualise various data.
Structure of application:
ChartPicker.vue (allow user to pick chart, create data, use dynamic component key to re-render component)
ChartWrapper.vue (receives props and passes them on, creates mixin for dynamic chart type)
ChartRender.vue (simply renders chart)
In the chart render component you usually you need to do 'extends: Bar', 'extends: Line' etc, therefore, requiring a ChartRender component type for each chart type. I found a neat solution that passes in the chart type to the chart mixins, then the final chart render makes no reference to chart type (not quite clear how this works even after looking at vue-chart.js code). This is the example I based my code on (it has no chart type selection):
https://codesandbox.io/s/vue-template-original-1czfi
So, I tried to extend functionality of that example to add a chart selector. It's working to an extent on chart type change: data changes, components re-render but the chart type doesn't change (even though it's being passed to the mixin dynamically)
I have a running example here:
https://codesandbox.io/s/vue-chart-issue-v2-twg3o
I've spent nearly a week trying to figure this out with no joy. I could create a workaround to use a separate ChartRender component for each chart type (e.g. ChartRenderBar, ChartRenderLine etc) but it moves away from DRY, so would rather not.
If anybody could help, I'd be VERY appreciative,
It is possible to dynamically update your chart type with vue-chartjs. The way I did it is by accessing the options in the chart itself and replacing it with the prop I get in which says which chart type it should become and then do an update on the chart. It is not the most elegant solution but it works.
<script>
import { Line, mixins } from 'vue-chartjs';
const { reactiveProp } = mixins;
export default {
extends: Line,
name: "LineChart",
mixins: [reactiveProp],
props: {
options: { type: Object },
chartType: { type: String }
},
mounted () {
this.renderChart(this.chartData, this.options);
},
watch: {
options: {
deep: true,
handler () {
this.$data._chart.options = this.options;
this.updateChart();
}
},
chartType (newVal) {
this.$data._chart.config.type = newVal;
this.updateChart()
}
},
methods: {
updateChart () {
this.$data._chart.update();
},
}
}
</script>
in Vuejs, it is not possible to change mixins after the component being created, and because of this I've used a wrapper component in my other solution, so I can pass a mixin dynamically before the component being created, but unfortunately, there is no way to have some kind of reactive mixin.
my solution for you current situation would be something like that:
https://codesandbox.io/s/vue-template-y5wsw
in the above solution, I have created two components, BarChart and LineChart
to switch between those dynamically, I am using one of the most awesome features in vuejs, Dynamic Component
and of course to avoid duplicating the data source, you can use vuex to share data between multiple components, or you can have the data in the parent page and access your dataset or options like
this.$parent['whatever data property in your parent component']
Hope you found this helpful.

Updating the values sent to child component on user click in vuejs

I have two components in mu app.vue and i will send data from app.vue to my first component(filter component) at the time of page load.
Now based on the user actions in the displayed data in the second component i need to pass new vales back to the first component.
There i am using a and a . Consider one of the props i receive in the first component is "nselectedOption" and i do this in data: { return { selectedOption: this.nselectedOption }} to avoid mutation warning.
Now everytime i update the values for this component from second component, i am seeing changes in "nselectedOption" only and not in "selectedOption". Can you explain why is that ?
I need the updated value into a v-model of .
1. If i use "nselectedOption" it is updating the textbox but while editing the value throws error.
2. If i use "selectedOption" it is not updating the values in the textbox itself.
I have even tried using the computed values to return the value, it works but if i try to change values in other options in the filter component the already updated values displays null or nothing.
Please help me. Is this problem can be solved using State Management Concept or do i have to have a separate compoenent other than App.Vue to do all this so that it would act as a parent/child kinda thing or is there anyother way to overcome this.
Try using watcher. If you watch for nselectedOption, everytime it changes, the watcher will fire and bind the changed value to selectedOption.
props: ['nselectedOption'],
data: {
selectedOption
},
watch: {
nselectedOption: function (val) {
this.selectedOption = val
}
}
Also, if the prop you are watching is an object/array, consider using spread operator if you want to make a local copy to avoid mutation.
this.someObj = { ...someProp }

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
}