I still have trouble why certain ways of changing data work, while others do not. I tried this example:
watch: {
'$store.state.linedata': function() {this.redraw()}
},
methods: {
redraw() {
this.chartOptions.series[0].data = this.$store.state.linedata
}
},
data() {
return {
chartOptions: {
chart: {
type: this.type
},
series: [{
data: this.$store.state.linedata,
name: "Test Series",
color: this.color
}]
}
}
}
This setup works, whenever I change the linedata in my store, the component gets updated. However, for me it would be more intuitive to update the data like this, without referencing this.chartOptions.series[0].data:
redraw() {
this.$store.state.linedata = [1,2,3,4,5,6]
}
This will update the state correctly, but however not cause to update the component with the new data. Why does the second way not work and is my first way the correct way to do it? I feel like I am missunderstanding some core concept here.What would a best practice look like?
Thank you!
From the Vuex docs you can read:
The only way to actually change state in a Vuex store is by committing a mutation
It means that you should not try to do this.$store.state.linedata = [1,2,3,4,5,6]. It may throw an error depending on your Vuex settings in the console by the way. Instead, create a mutation like so:
mutations: {
updateLineDate(state, lineDate) {
state.lineData = lineData;
}
}
And then call:
this.$store.commit("updateLineDate", [1, 2, 3, 4, 5, 6]);
To automatically update your chart's data, I would suggest creating a computed property in your Vue component. To make it reactive to changes, map your attribute using mapState:
import { mapState } from "vuex";
// ...
computed: {
...mapState("lineData"),
chartData() {
return {
chart: {
type: this.type
},
series: [{
data: this.lineData,
name: "Test Series",
color: this.color
}]
}
}
}
Then remember to provide chartData to your chart component instead of chartOptions in my example.
Related
So I have been trying to make a linechart work with Echarts. I made this LineChart.vue and expect it to get props, which are arrays, from its father component as options data of Echarts.
But the props, which are proxies of arrays, doesn't seem to work well. It is shown in the console that it has the right target, but this proxy is not recognized by Echarts, so there was no data on my chart.
And to make it wierder to me, I accidently found out that if I keep my terminal open, make some changes to the code (which is nothing but comment and uncomment the same lines), and save it (which probably rerends this component), the props somehow works and the linechart actually shows up! But if I refresh the page, the data goes blank again.
Here is my code:
<template>
<div id="chart"></div>
</template>
<script>
let chart;
export default {
data() {
return {
option: {
name: "demo",
xAxis: {
type: "category",
data: [],
},
yAxis: {
// type: "value",
},
series: [
{
data: [],
type: "line",
},
],
},
};
},
props: {
xAxisData: Array,
seriesData: Array,
},
methods: {
initChart() {
chart = this.$echarts.init(document.getElementById("chart"));
// these are the four lines that I commented and uncommented to make things wierd
this.option.xAxis.data = this.xAxisData;
this.option.series[0].data = this.seriesData;
console.log(this.option.xAxis.data);
console.log(this.option.series[0].data);
chart.setOption(this.option);
},
},
mounted() {
this.initChart();
},
watch: {
xAxisData: {
handler: function (newData) {
this.option.xAxis.data = newData;
},
deep: true,
},
seriesData: {
handler: function (newData) {
this.option.series[0].data = newData;
},
deep: true,
},
},
};
</script>
<style scoped>
#chart {
height: 250px;
width: 400px;
}
</style>
And here iswhat is the proxy like before and after I made some minor changes to the code
I also tried to turn this proxy xAxisData into an object using Object.assign(), but it turns out to be empty! I am starting to think that it might have somthing to do with component life cycle, but I have no clue when and where I can get a functional proxy. Can someone tell me what is actually going on?
FYI, here are value of props in console and value of props in vue devtool.
Just figured it out. Just so you know, the info provided above was insufficient, and I made a noob move.
My vue component was fine, it was async request that caused this problem. The data for my Echarts props is requseted through a Axios request, and my child-component (the linechart) was rendered before I got the data. Some how, the proxies of the arrays donot have the data, yet they got the target shown right. By the time my child-component got the right data, the Echart was already rendered with outdated options data, which by the way was empty. And that is why re-render it can show us the data. It has nothing to do with proxy, proxy works just fine. It is me that needs to pay more attention to aysnc movement. Also, I learned that obviously Echarts was not reactive at all, so I watched the props and updated the option like this:
watch :{
xAxisData: {
handler: function (newData) {
this.option.xAxis.data = newData;
this.chart.clear();
this.chart.setOption(this.option);
},
deep: true,
},
}
It works.
It seems to me that I found some sort of bug. Basically I want to get components object by index (for the tag). But I've experienced a strange issue. I've included necessary pieces of my code below:
Working example:
let steps = ['Handler', 'Categories', 'Finalize'];
export default {
components: {
Handler,
Categories,
Finalize
},
data() {
return {
step: 0,
currentStep: steps[0] // When specifying index without a variable
}
},
}
Broken example:
let steps = ['Handler', 'Categories', 'Finalize'];
export default {
components: {
Handler,
Categories,
Finalize
},
data() {
return {
step: 0,
currentStep: steps[this.step] // When specifying index by a variable
}
},
}
In working example I am getting component (as expected), but in broken I am getting currentStep: undefined in Vue DevTools. However, no errors in the console. What am I doing wrong?
Your best bet is to move the currentStep to a computed property. Also, steps need to exist in data so they are reactive:
let steps = ['Handler', 'Categories', 'Finalize'];
export default {
components: {
Handler,
Categories,
Finalize
},
data() {
return {
step: 0,
steps,
}
},
computed: {
currentStep() {
return this.steps[this.step];
}
}
}
If possible, prefer to stick the steps directly in data:
data() {
return {
step: 0,
steps: ['Handler', 'Categories', 'Finalize'];,
}
},
(But that may not be possible if you're importing them from the outside. I don't know about your specific use case).
In general, in Vue, when something directly depends on the value of some component properties, computed properties are the way to go: they are performant and clear.
In your original code, should it have worked, currentStep would not react to a change in step. Using a computed property, instead, whenever the step updates, the currentStep will update accordingly.
Suppose I have an array feedsArray, the example value may look like this:
this.feedsArray = [
{
id: 1,
type: 'Comment',
value: 'How are you today ?'
},
{
id: 2,
type: 'Meet',
name: 'Daily sync up'
}
]
Suppose I have registered two components: Comment and Meet, Each component has a prop setting as the following:
props: {
feed: Object
}
and the main component has the following definition:
<component v-for="feed in feedsArray" :feed="feed" :key="feed.id" :is="feed.type"></component>
As you can see, it uses is property to select different component. My question is, how to detect feed object change in the child component ? Like when I set
this.feedsArray[0] = {
id: 1,
type: 'Comment',
value: 'I am not ok'
}
How can the Comment component detect the changes ? I tried to add a watcher definition in the child component like the following:
watch: {
feed: {
handler (val) {
console.log('this feed is changed')
},
deep: true
}
},
But it doesn't work here. Anyone know how to solve this ?
Do not assign directly to an array using index - use splice() instead, otherwise JavaScript can not detect that you have changed the array.
If you want to change only the value of an already existing key of an object - then simply update it e.g. this.feeds[0].value = 'I am not okay any more';
This works for existing keys only - otherwise you have to use this.$set(this.feeds[0], 'value', 'I am not okay any more');
In my project for my clan, Lords of War on the Supercell games. I am trying to make a chart for the current donations with chart.js. I'm using Vue for the front-end and vue-chartjs for the charts. There is only 1 problem. When i open the page, the datasets are not visible. So how can i fix that?
this is the chart data object:
donation_chart_data: {
labels: [],
datasets: [
{
label: 'Donated',
hidden: false,
backgroundColor: '#1D93D3',
data: []
},
{
label: 'Received',
hidden: false,
backgroundColor: '#C7031F',
data: []
}
]
}
This is the DonationChart component:
import { Bar, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins;
export default {
extends: Bar,
name: 'ClashRoyaleDonationChart',
mixins: [reactiveProp],
mounted () {
// Overwriting base render method with actual data.
this.renderChart(this.chartData,
{
responsive: true,
maintainAspectRatio: false
})
}
}
PS: when I click the legend, the data is displayed properly
Well, I guess you are using an API to get your data?
Then you need to check if the data is available. Axios / Ajax requests are async. So most of the time, your chart will be rendered without data.
Just add a v-if="loaded" on your chart component and in your
axios.get().then() method, set loaded = true and you should be fine.
I have an list of objects in firebase called meals.
In this component I want to show information on only one meal so I pass an id field as a prop and I want to get only that meal from firebase.
This is what I tried. It didn't work because this.id was undefined:
import db from '#/firebase'
export default {
name: 'meal',
props: {
id: {
type: String,
required: true
}
},
firebase: {
meals: db.ref('meals').child(this.id)
}
}
Did I do something wrong or does the firebase call happens before the props are initialized?
EDIT:
I managed to do it using the created hook but it looks pretty bad. Is there any other way?
created() {
this.$bindAsObject('meal', db.ref('meals').child(this.id))
}
This one burned me pretty hard. You have to use the firebase function AND create a reference to your variable like this:
import db from '#/firebase'
export default {
name: 'meal',
props: {
id: {
type: String,
required: true
}
},
firebase() {
const id = this.$props.id // Pass the reference instead of the prop directly
return {
meals: db.ref('meals').child(id)
}
}
}
According to the official doc of VueFire, your way to use the creation hook is exactly correct. Firebase basically works in a different lifecycle with Vue.js, so you need to use that doller syntax provided VueFire.