VueJs + Axios + D3.js "Error: too late; already running" - javascript

I am trying to visualize some data with Vue-d3-charts.
I am getting the data asyncronously with axios.
The problem is that i am having issues with pushing the response data to the chart_data.
Either i get the error that my variable(buildingdata) with the response is undefined (probably because the response arrives later than everything else and therefore ) or Error in callback for watcher "datum": "Error: too late; already running.
Now i have confirmed that i am indeed getting a valid response by console logging the response to my browser.
What am i doing wrong?
Here is the vue code in my component
<template lang="pug">
div
.card
.card-body
//here is where the D3 component is
<D3BarChart :config="chart_config" :datum="chart_data" :title="chart_title"></D3BarChart>
</template>
<script>
import { D3BarChart } from 'vue-d3-charts';
import axios from "axios;
export default {
components: {
D3BarChart,
},
created(){
console.log("created function ran")
const sendGetRequest = async () => {
try {
let response = await axios.get("path_to_endpoint");
let buildingdata = response.data;
console.log(buildingdata)
//the code runs fine until i get here and try to push the response data to the chart_data.
this.chart_data.push(...buildingdata)
}
catch (error) {
console.log(error);
}
};
sendGetRequest();
},
data() {
console.log("data setup ran")
return {
chart_title: "Title",
// Chart data, this is where D3 gets its data from.
chart_data: [
],
// This is the chart config. it is from here you can change the chart type, colors, etc.
chart_config: {
key: 'name',
values: ['value'],
orientation: 'vertical',
},
}
},
}
</script>
Here is the output from my browser console
App mounted App.vue:35
data setup ran building.vue:36
created function ran building.vue:20
You are running Vue in development mode.
Make sure to turn on production mode when deploying for production.
See more tips at https://vuejs.org/guide/deployment.html vue.esm.js:9132
Array(5) [ {…}, {…}, {…}, {…}, {…} ]
[Vue warn]: Error in callback for watcher "datum": "Error: too late; already running"
found in
---> <D3BarChart>
<OpenWoPerBuilding> at app/javascript/apps/new_insights/components/open_wo_per_building.vue
<App> at app/javascript/apps/new_insights/App.vue
<Root> vue.esm.js:628
VueJS 15
_callee$ open_wo_per_building.vue:26
tryCatch app.js:221
_invoke app.js:221
defineIteratorMethods app.js:221
asyncGeneratorStep app.js:235
_next app.js:237
(Async: promise callback)
asyncGeneratorStep app.js:235
_next app.js:237
_asyncToGenerator app.js:237
_asyncToGenerator app.js:237
sendGetRequest open_wo_per_building.vue:21
created open_wo_per_building.vue:32
VueJS 41
js app.js:12
(Async: EventListener.handleEvent)
js app.js:9
Webpack 7
Error: too late; already running
set schedule.js:42
tweenFunction tween.js:31
__WEBPACK_DEFAULT_EXPORT__ each.js:5
__WEBPACK_DEFAULT_EXPORT__ tween.js:67
__WEBPACK_DEFAULT_EXPORT__ attrTween.js:43
__WEBPACK_DEFAULT_EXPORT__ attr.js:74

You need to wait to have some data before displaying your component. The reason being that the template is synchronous and is not waiting for your data to be fetched.
This can be solved with a small v-if expecting your data to be fetched.
<D3BarChart v-if="asyncDataDoneLoading"
:config="chart_config"
:loaded="loaded"
:datum="chart_data"
:title="chart_title">
</D3BarChart>

So #kissu got me on the right track with the conditional rendering tip. What i did was use a flag that with a default of false in the "data()" that returns true after data has been fetched from axios in "created()".
<template lang="pug">
div(v-if="loaded")
.card
.card-body
<D3BarChart :config="chart_config" :loaded="loaded" :datum="chart_data" :title="chart_title"></D3BarChart>
</template>
<script>
import { D3BarChart } from 'vue-d3-charts';
import axios from "axios";
export default {
components: {
D3BarChart,
},
created(){
const sendGetRequest = async () => {
try {
let response = await axios.get("path_to_endpoint");
let buildingdata = response.data;
this.chart_data.push(...buildingdata)
this.loaded=true
}
catch (error) {
console.log(error);
}
};
sendGetRequest();
},
data() {
return {
loaded: false,
chart_title: "Title",
chart_data: [],
chart_config: {
key: 'name',
values: ['value'],
color: {name: '#7f3727'},
orientation: 'vertical',
},
}
},
}
</script>

Related

Why it sends multiple request, when I call it only once?

My project were working fine. I just found out in console network that one of my GET request is sending twice, even I just send it once. See network console
If I comment the the whole code of created function, all GET request would no longer load/exist in the console network. (see code below)
I want to know what causes this, and how should I fix this?
Here is the Component.vue
<script>
export default {
created: async function() {
await this.$store.dispatch('file/all');
},
};
</script>
And the vuex module post.js's action:
const actions = {
all({commit}, data) {
return axios.get(`files`)
.then(response => {
commit('setData', response);
});
},
}
After many hours of searching, I found out that the key that is assigned to the Component caused the problem.
When the key is modified the GET request will send again. This the reason why it sends twice. Special thanks to #Anatoly for giving me the hint.
Below is the usage codes:
<template>
<Component :key="componentKey" #edit="dataIsChanged"/>
</template>
<script>
export default {
components: { Component },
data: () => ({
componentKey: 0,
}),
methods: {
dataIsChanged: function() {
this.componentKey = Math.random();
}
}
};
</script>

Websockets in Vue/Vuex (how to receive emissions from server)

So until now I just socket.io-client to do communication to a WebSocket in my Vue component.
Now I am adding Vuex to the project and declared a Websocket like this
Vue.use(new VueSocketIO({
debug: true,
connection: 'http://192.168.0.38:5000',
}));
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
1) Should i have stuff like emitting some messages in the component themselves now or in the store?
2) Before I introduced the changes I could do something like this:
socket.on('connect', function () {
console.error('connected to webSocket');
socket.emit('my event', { data: 'I\'m connected!' });
});
socket.on('my response', function(data){
console.log('got response');
console.log(data.data);
});
When sending the "my event", the flask server would respond with "my response". Now I am trying the same thing from a component after the changes like this.
this.$socket.emit('my_event', { data: 'I\'m connected!' });
console.error('send to websocket ');
this.$options.sockets.my_event = (data) => {
console.error('received answer ');
console.error(data);
};
The my_event reaches my flask server however I don't get the response receiving to work. What am I doing wrong?
Also because I was asking about whether I should put this in the component or the store, I found stuff like this for the store:
SOCKET_MESSAGECHANNEL(state, message) {
state.socketMessage = message
}
The explanation was "So, for example, if your channel is called messageChannel, the corresponding Vuex mutation would be SOCKET_MESSAGECHANNEL" and it is from this site https://alligator.io/vuejs/vue-socketio/.
I think I don't really get what a channel is at this point. Is the my_response I emit from the flask server also a channel?
Thanks for your help in advance!
EDIT: So now I am trying to listen and emit to a websocket from my store. For this I tried the following: In main.js I have this part:
Vue.use(new VueSocketIO({
debug: true,
connection: SocketIO('http://192.168.0.38:5000'),
vuex: {
store,
actionPrefix: 'SOCKET_',
mutationPrefix: 'SOCKET_',
},
}));
Then in my store.js I have the following:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0,
title: 'title from vuex store',
isConnected: false,
},
mutations: {
increment(state) {
state.count += 1;
},
emitSth(state) {
this.sockets.emit('my_event', { data: 'I\'m connected!' });
console.log(state.count);
},
SOCKET_my_response(state) {
state.isConnected = true;
alert(state.isConnected);
},
SOCKET_connect(state) {
state.isConnected = true;
alert(state.isConnected);
},
},
});
And in my component I have this script:
export default {
name: 'ControlCenter',
data() {
return {
devices: [{ ip: 'yet unknown' }], // placeholder so line 12 does not throw error before actual device info fetched
thisDeviceIndex: 0,
currentLayoutIndex: 0,
layouts: [],
};
},
computed: mapState([
'title',
'count',
]),
components: {
DNDAssign,
FirstPage,
},
methods: {
// mapMutation helper let's us use mutation from store via this instead of this.$store
...mapMutations([
'increment',
'emitSth',
]),
incrementMutation() {
this.increment();
},
emitEvent() {
this.emitSth();
},
// some other stuff here
},
created() {
// inital fetching of layouts
console.log('fetching layouts from backend');
this.getAllLayouts();
console.log(this.$socket);
},
};
I also have a button for the triggering of the emit which is
<b-button
type="button"
variant="success"
v-on:click="emitEvent()"
>
emit event
</b-button>
The connected in the store gets triggered, however I get the following errors for the emitting:
"TypeError: Cannot read property 'emit' of undefined"
"Cannot read property 'emit' of undefined"
Also I am not sure about the naming in the mutations. If I have this mutationPrefix, shouldn't it be enough to just use connect instead of SOCKET_connect?
First of all, if you are using Vue-Socket.io version 3.0.5>, uninstall it and install version 3.0.5
npm uninstall vue-socket.io
npm install vue-socket.io#3.0.5
then lock the version in packege.json: "vue-socket.io": "3.0.5", latest update seems to breaks the library, read more here
Now to receive events from socket.io server, you can do:
this.sockets.subscribe("my response", (data) => {
console.log(data);
});
or if want put listener on component level, you need to add sockets object on the component export, for example:
export default {
...
sockets: {
"my response": function (data) {
console.log(data);
}
}
...
}
Since you are not using Vuex Integration on the VueSocketIO, you dont need to put additional function in store mutation. If you want to use Vuex integration on VueSocketIO, you need to add vuex object when declaring the VueSocketIO class.
Here's the basic example for main.js
// Set Vue to use Vuex
Vue.use(Vuex);
// Create store
const store = new Vuex.Store({
state: {
someData: null
},
getters: {},
actions: {
"SOCKET_my response"(context, data) {
// Received `my response`, do something with the data, in this case we are going to call mutation "setData"
context.commit("setData", data);
}
}
mutations: {
["setData"](state, data) {
state.someData = data; // Set it to state
}
}
});
// Set Vue to use VueSocketIO with Vuex integration
Vue.use(new VueSocketIO({
debug: true,
connection: 'http://192.168.0.38:5000',
vuex: {
store,
actionPrefix: "SOCKET_"
}
}));
new Vue({
router,
store
render: h => h(App)
}).$mount("#app");
If you need example on Vuex Integration, you can check my example app that uses Vue and Vue-Socket.io with Vuex integration.te

Exported data is null when using it in another javascript vue js file

i am developing a shopify theme, currently i am working on shopping cart page, i have CartForm.js file where i am importing data from cartData.js file. This is CartForm.js...
import { store } from "./../shared/cartData.js";
if (document.querySelector('.cart-form')) {
let productForm = new Vue({
el:".cart-form",
delimiters: ['${', '}'],
data(){
return{
cart:null,
}
},
created(){
this.getCart();
},
methods:{
getCart(){
store.getCart();
this.cart=store.state.cartData;
console.log("cart data: "+this.cart);
},
and this is cartData.js file
export const store = {
state: {
cartData:null
},
getCart(){
alert("store get cart called!...")
axios.get('/cart.js')
.then( response => {
this.state.cartData=response.data;
console.log("Responsed data="+this.state.cartData);
})
.catch( error => {
new Noty({
type: 'error',
layout: 'topRight',
text: 'There was an error !!'
}).show();
});
}
}
As you can see i am explicitly calling store.getCart(); in CartForm's getCart() method, and when "Responsed data" gets printed it shows that this.state.cartData filled with data, but when i am using it like this: this.cart=store.state.cartData; this.cart is null, why is null? Any help is appreciated
This happens because your API takes a while to respond, but JavaScript continues running the function in parallel. Your state is still 'null' while the call hasn't returned, thus this.cart will be set to null, unless you tell JavaScript to wait until the call is finished.
Try making the getCart() method an asynchronous function:
methods:{
async getCart(){
await store.getCart();
this.cart=store.state.cartData;
console.log("cart data: "+this.cart);
},
If cart should always be the same as the data in your store, a better way to do this might be to use a computed property for cart. This returns the store.state directly and will help you keep a single source of truth for your data.
computed: {
cart() {
return store.state.cartData
}
}

Stock chart in Highcharts-vue in vue-cli project: data grouping not working

I am trying to display some data in a stock chart within a Vue.js app, using the highcharts-vue wrapper.
I have encountered a problem when the dataGrouping (active by default) is triggered when the numbers of points in my chart becomes too big. I get the following error:
[Vue warn]: Error in callback for watcher "options": "TypeError: F is undefined"
However, if I deactivates the dataGrouping option, my chart renders fine.
I have tried to overwrite the approximation function (which is supposed to be the average by default for a line chart), but even though I can see that my custom function works well, I get the error anyway.
I tried to make a jsfiddle to reproduce my bug but I didn't find a way to create a stock chart using the highcharts-vue wrapper.
EDIT: I created a sandbox with a stripped-down version of the chart with a sample of my data, but it works well and doesn't reproduce the bug. When I use the same code inside of my app (you can see it here on gitlab), I get my error.
(Actually I get several kinds of errors. When I use vue-cli-service serve and display in Firefox, it says Error in mounted hook: "TypeError: y is undefined", but in Chrome it says Error in mounted hook: "TypeError: Cannot read property 'x' of undefined", and finally when I build the error becomes TypeError: "w is undefined" in Firefox, stays the same in Chrome.)
Here is a full error stack trace:
[Vue warn]: Error in mounted hook: "TypeError: y is undefined"
found in
---> <Highcharts>
<LineChart> at src/components/LineChart.vue
<GridItem> at GridItem.vue
<GridLayout> at GridLayout.vue
<Home> at src/views/Home.vue
<App> at src/App.vue
<Root> vue.runtime.esm.js:601
TypeError: "y is undefined"
translate webpack-internal:///./node_modules/highcharts/highcharts.js:316:361
renderSeries webpack-internal:///./node_modules/highcharts/highcharts.js:279:498
renderSeries webpack-internal:///./node_modules/highcharts/highcharts.js:279:466
render webpack-internal:///./node_modules/highcharts/highcharts.js:281:384
<anonymous> webpack-internal:///./node_modules/highcharts/modules/stock.js:134:7
c webpack-internal:///./node_modules/highcharts/highcharts.js:21:233
<anonymous> webpack-internal:///./node_modules/highcharts/modules/stock.js:134:7
c webpack-internal:///./node_modules/highcharts/highcharts.js:21:233
firstRender webpack-internal:///./node_modules/highcharts/highcharts.js:284:407
init webpack-internal:///./node_modules/highcharts/highcharts.js:258:369
fireEvent webpack-internal:///./node_modules/highcharts/highcharts.js:32:244
init webpack-internal:///./node_modules/highcharts/highcharts.js:257:119
getArgs webpack-internal:///./node_modules/highcharts/highcharts.js:257:39
Chart webpack-internal:///./node_modules/highcharts/highcharts.js:256:337
stockChart webpack-internal:///./node_modules/highcharts/modules/stock.js:139:135
mounted webpack-internal:///./node_modules/highcharts-vue/dist/module/highcharts-vue.min.js:1:1514
callHook webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:3037:9
insert webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:4255:7
invokeInsertHook webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:6046:28
patch webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:6265:5
_update webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2777:16
updateComponent webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2898:7
get webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:3268:13
Watcher webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:3257:7
mountComponent webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:2905:3
mount webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:8060:10
<anonymous> webpack-internal:///./src/main.js:39:4
js http://localhost:8080/app.js:2774:1
__webpack_require__ http://localhost:8080/app.js:767:12
fn http://localhost:8080/app.js:130:20
0 http://localhost:8080/app.js:2847:18
__webpack_require__ http://localhost:8080/app.js:767:12
<anonymous> http://localhost:8080/app.js:902:18
<anonymous> http://localhost:8080/app.js:1:11
I downloaded the sandbox code locally and tried to put my app code in it by removing extra things but it wouldn't work, and putting the simple Chart in my original app wouldn't work either.
So I guess the problem might be in mixing the use of vue-cli, vue-router and vue-loader with the use of highcharts-vue. It's possible it is only a simple initialization or import step that I am doing wrong...
Here is the simplified code of my component. The creation of the series is triggered by the modification of a list of probes (checkedProbesOnline) from I which I fetch the data through a call to an API:
<template>
<highcharts :constructor-type="'stockChart'"
:options="config"
:updateArgs="[true, true, true]"
ref="myChart"
style="width: 0; height: 0;"
class="chart"
>
</highcharts>
</template>
<script>
import axios from 'axios'
import Highcharts from 'highcharts';
import stockInit from 'highcharts/modules/stock';
stockInit(Highcharts);
export default {
name: "LineChart",
props: {
checkedProbes: Array,
name: String,
suffix: String,
callName: String
},
data() {
return {
series: []
}
},
computed: {
checkedProbesOnline() {
return this.checkedProbes.filter(probe => probe.online);
},
config() {
return {
series: this.series
};
}
},
watch: {
checkedProbesOnline(newValue) {
this.series = [];
this.createChart();
}
},
mounted() {
this.refresh();
},
methods: {
createSerie(name, data) {
return {
name: name,
data: data,
tooltip: {
valueDecimals: 1,
valueSuffix: this.suffix
},
dataGrouping: {
approximation: function() {
let start = this.cropStart + this.dataGroupInfo.start;
let rawData = this.options.data.slice( start, start + this.dataGroupInfo.length );
return (rawData.length > 0) ? rawData.map(a => a[1]).reduce((a, b) => (a + b))/this.dataGroupInfo.length : 0;
}
}
}
},
async getSerie(probe, dateStart, dateEnd) {
await axios.get('http://example.com')
.then(response => response.data)
.then(data => {
let serieData = [];
for (let i = 0; i < data.data.length; i++) {
let timestamp = new Date(data.data[i].measurements.date);
serieData.push([timestamp.getTime(), data.data[i].measurements[this.callName]]);
}
let serie = this.createSerie(probe.id, serieData);
this.series.push(serie);
})
.catch(err => {
console.log(err);
});
},
async createChart() {
let now = Date.now()/1000;
let before = now - (60 * 60);
let promises = [];
for (let probe of this.checkedProbesOnline)
promises.push(this.getSerie(probe, before, now));
await Promise.all(promises).then(this.refresh);
},
refresh() {
this.$refs.myChart.$el.style.width = '100%';
this.$refs.myChart.$el.style.height = '100%';
this.$refs.myChart.chart.reflow();
}
}
}
</script>
If you want to see the JSON data returned by my API, I copied it here: gist
I looked everywhere for someone getting the same bug but didn't find anything so any help or advice would be immensely appreciated.

Using Axios and Vue to fetch api data - returning undefined

Running into a snag with trying to integrate my API with Vue/Axios. Basically, Axios is getting the data (it DOES console.log what I want)... But when I try to get that data to my empty variable (in the data object of my component) to store it, it throws an "undefined at eval" error. Any ideas on why this isn't working for me? Thanks!
<template>
<div class="wallet-container">
<h1 class="title">{{ title }}</h1>
<div class="row">
{{ thoughtWallet }}
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'ThoughtWallet',
data () {
return {
title: 'My ThoughtWallet',
thoughtWallet: [],
}
},
created: function() {
this.loadThoughtWallet();
},
methods: {
loadThoughtWallet: function() {
this.thoughtWallet[0] = 'Loading...',
axios.get('http://localhost:3000/api/thoughts').then(function(response) {
console.log(response.data); // DISPLAYS THE DATA I WANT
this.thoughtWallet = response.data; // THROWS TYPE ERROR: Cannot set property 'thoughtWallet' of undefined at eval
}).catch(function(error) {
console.log(error);
});
}
}
}
</script>
Because you're using .then(function(..) { }) this won't refer to the vue context this.
You have two solutions, one is to set a variable that references the this you want before the axios call, e.g.:
var that = this.thoughtWallet
axios.get('http://localhost:3000/api/thoughts').then(function(response) {
console.log(response.data); // DISPLAYS THE DATA I WANT
that = response.data; // THROWS TYPE ERROR: Cannot set property 'thoughtWallet' of undefined at eval
}).catch(function(error) {
console.log(error);
});
The other is to use the new syntax (for which you need to make sure your code is transpiled correctly for browsers that don't support it yet), which allows you to access this inside the scoped body of the axios then.
axios.get('http://localhost:3000/api/thoughts').then((response) => {
console.log(response.data); // DISPLAYS THE DATA I WANT
this.thoughtWallet = response.data; // THROWS TYPE ERROR: Cannot set property 'thoughtWallet' of undefined at eval
}).catch(function(error) {
console.log(error);
});
The reason this happens is because inside that function/then, this will be referring to the context of the function, hence there won't be a thoughtWallet property
this.thoughtWallet inside the .get method is referring to the axios object, not Vue's. You can simply define Vue's this on the start:
methods: {
loadThoughtWallet: function() {
let self = this;
this.thoughtWallet[0] = 'Loading...',
axios.get('http://localhost:3000/api/thoughts').then(function(response) {
console.log(response.data); // DISPLAYS THE DATA I WANT
self.thoughtWallet = response.data;
}).catch(function(error) {
console.log(error);
});
}
}

Categories

Resources