ApexCharts & Vue - changing prop values does not update chart in chart component - vue.js

I have an ApexCharts chart in a separate component, and the series values are passed in as a prop.
My parent component makes some calculations in beforeMount(), which determines the values in the array that is the prop.
I don't know why, but it's not updating dynamically.
ApexCharts documentation says:
You don't actually need to call updateSeries() or updateOptions() manually
And I would expect Vue to update the values like this automatically.
I have checked by displaying the variable passed as a prop in the parent component, and the values are updating after the calculations are made. Do I need to do something extra to 'push' these through to the child component again, after this?

I've found a workaround, be it's still surprising to me that it doesn't update automatically.
From: https://michaelnthiessen.com/force-re-render/
If I assign a key to the component, like this:
<Chart :values='this.values' :key="componentKey" />
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
And then call the forceRerender() method after I've done my calculations for this.values, everything seems fine -- the chart displays with the correct values from the calculated prop.

Related

Is there any way to change props value of a Vue Component from method?

I'm using two "vuejs-datepicker" components in my Vue when i click on of the datepicker which is been named as a "activityFrom" and select the date upon the closing the datepicker I'm triggering the component defined function "#selected" where am I need to the change the props of second datepicker "activityTo" which is disabledDates props.
This is my first component
<datepicker #selected="changeNextDate" v-model="$v.fields.activityFrom.$model" :disabledDates="disabledDates" ></datepicker>
And this is my second component
<datepicker ref="activityTo" v-model="$v.fields.activityTo.$model" :disabledDates="disabledDates" ></datepicker>
This is my code
<script>
import Datepicker from 'vuejs-datepicker';
import moment from 'moment';
export default {
name:'Home',
data(){
return{
fields:{
destination:'',
activityFrom:'',
activityTo:'',
},
disabledDates:{
to: new Date((new Date().getTime()+(2*24*60*60*1000)) - 8640000)
},
disabledDates2:{
to: new Date((new Date().getTime()+(3*24*60*60*1000)) - 8640000)
}
}
},
components:{
Datepicker
},
methods:{
customFormatter(date) {
return moment(date).format('MM/DD/YYYY');
},
changeNextDate(event){
console.log('Date has been seleced');
this.$refs.activityTo.disabledDates = disabledDates2;
}
}
}
</script>
But when i tried to change the props from method it is sending me the following error
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:
"disabledDates"
Is there any solution for changing the props becasue i really do change the second element upon clicking on the function of first element. Please tell me how to fix this.
You don't need to access this.$refs.activityTo in order to update the props for your second component. Since the props are being passed down from your parent you can just update them there, e.g.:
changeNextDate(event){
this.disabledDates = this.disabledDates2;
}
Also I'm not sure why you're using that v-model syntax; I've never seen that before and I don't think it should be necessary. You can just do it like this: v-model="fields.activityFrom" and v-model="fields.activityTo".

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.

Vuetify v-data-table drag and drop

I am using V-data-table along with vuex store. Below are the points how I configured my v-data-table
Disabled sort on each column
bind the v-data-table items with vuex state store data
using sortablejs to drag and drop the rows
Problem:
When I drag and drop the rows in the v-data-table the I am updating the vuex store(updating the index value on the objects in array with the table row index value). Vuex is updating properly but the data rendered in the v-data-table is not in the order as they are in the vuex state store
Could someone help me on this
The best way I tried to overcome this problem is to force re-render the v-data-table component, but when I do this I cannot drag and drop anymore
Force rendered using the following the template
<template>
<component-to-re-render :key="componentKey" />
</template>
// script
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
This might not be the optimal solution, but I had a similar problem except I was just using a regular Array. I managed to fix it by calling the Sortable.create() method in updated() life cycle hook.
My guess is that when you call Sortable.create(table, ...) it refers to that instance of the table element. However, when you try to change the table by modifying the key, it changes that instance. Therefore, you need to call Sortable.create() again whenever the vue component is updated.

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 to Initialize Data Properties with Prop Values

Still a little bit young in VueJS but I'm loving every bit of it. But now, fixated somewhere.
I want to initialize some values in data() using values passed via props. This is so that I can be able to mutate them later on, since it is not recommended to mutate props inside a component. In fact the official docs recommend this property initialization using prop values as shown below:
{
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
I have something like the one below:
<template>
<div class="well">
<!-- Use Prop value directly on the template: works (but of no help in initializing data) -->
Department: {{department.name}}
<!-- Use prop value but gotten via computed property: Works inside the template but not in the initialization -->
Department: {{fetchDepartment.name}}
<!-- Use the array I initialized with the prop value: Does not work -->
Department: {{this_department.name}}
</div>
</template>
<script>
export default {
name: 'test',
props: ['department'],
data() {
return {
this_department: this.department
// below does not work either
//this_department: this.fetchDepartment
}
},
created() {
// shows empty array
console.log(this.department)
},
mounted() {
// shows empty array
console.log(this.department)
},
computed: {
fetchDepartment() {
return this.department
}
}
}
</script>
As seen in the commented sections above, the initialization is not successful. Neither does the value of this.department appear either from the created() or the mounted() hooks. And note, I can see it is defined using the Chrome Vue Devtools. So my question is, how exactly should I initialize data() attributes using props values, or which is the best way of going around this issue?
I know my answer comes in late but it helps me and hopefully someone else coming here. When props' data are async:
// in the parent component
<template>
<child :foo="bar" v-if="bar" />
</template>
That way, you render the component when props are already available making it safer to follow the guide's recommended ways to initialize data value with props as so:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
Happy coding!
You CAN modify a prop. Use the '.sync' modifier. I use it frequently because it is convenient and intuitive. This requires emitting an event to update the value on the parent. I am not really sure the warning of how it results in maintenance issues.
Another method I use if I want to modify a value and not update the parent is using Lodash clone. For example (assuming its available on mounted)
mounted(){
this_department = _.clone(this.department)
}
If you consistently want to mutate the prop and have it change with the parent, then use a computed property. However, in most cases you will want to depend on the state of that data within the component and change it using other functions and thus a computed property will not be what you need.
A computed property is the simplest way to provide a mutable version of a prop, but you might not want to lose data when the prop is updated. You could use an explicit watch.
Watchers
While computed properties are more appropriate in most cases, there
are times when a custom watcher is necessary. That’s why Vue provides
a more generic way to react to data changes through the watch option.
This is most useful when you want to perform asynchronous or expensive
operations in response to changing data.
This is most useful when you want to perform asynchronous or expensive
operations in response to changing data.