I am trying to fetch some data from an API, do some data formatting, and then feed that data to my Chart component but I get the following error when trying to do so:
Uncaught (in promise) Error: Maximum recursive updates exceeded. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.
This is how my code looks like:
<template>
<apexchart type="rangeBar" :options="chartOptions" :series="sceneAttributes"></apexchart>
</template>
<script>
import { ref } from 'vue';
setup() {
const sceneAttributes = ref([]);
fetch(`apiUrl`)
.then(resp => resp.json())
.then(data => {
// const atts = [];
for (const key in data.sceneAttributeValues) {
data.sceneAttributeValues[key].forEach(att => {
let obj = {};
obj.data = [];
obj.data.push({
x: att.attributeValue.attributeId,
y: [att.from, att.to+1]
})
// obj.from = att.from;
// obj.to = att.to;
if (att.attributeValue.strValue) {
obj.name = att.attributeValue.strValue;
}
if (att.attributeValue.intValue) {
obj.name = att.attributeValue.intValue;
}
if (att.attributeValue.boolValue) {
obj.name = att.attributeValue.boolValue;
}
sceneAttributes.value.push(obj)
})
}
// End of first for loop
});
return { sceneAttributes }
}
}
I have to do this convoluted data formatting because the raw data I get from the fetched JSON.
{
"sceneAttributeValues": {
"Object Distance": [
{
"attributeValue": {
"attributeId": "Object Distance",
"intValue": 39
},
"from": 29,
"to": 123
}
],
"Object Detection": [
{
"attributeValue": {
"attributeId": "Object Detection",
"strValue": "Yes"
},
"from": 79,
"to": 122
}
],
}
}
Any hint on what I'm doing wrong would be greatly appreciated.
I guess using AXIOS for requesting an API might help you
Here is the document on how to use axios in Vue : Using Axios to Consume APIs
Don't forget to install and import axios before using it , To install axios you need to run this command in your project directory :
npm i axios
Here you can find more info about axios : axios-npm
Related
I am using XState as a state manager for a website I build in Nuxt 3.
Upon loading some states I am using some asynchronous functions outside of the state manager. This looks something like this:
import { createMachine, assign } from "xstate"
// async function
async function fetchData() {
const result = await otherThings()
return result
}
export const myMachine = createMachine({
id : 'machine',
initial: 'loading',
states: {
loading: {
invoke: {
src: async () =>
{
const result = await fetchData()
return new Promise((resolve, reject) => {
if(account != undefined){
resolve('account connected')
}else {
reject('no account connected')
}
})
},
onDone: [ target: 'otherState' ],
onError: [ target: 'loading' ]
}
}
// more stuff ...
}
})
I want to use this state machine over multiple components in Nuxt 3. So I declared it in the index page and then passed the state to the other components to work with it. Like this:
<template>
<OtherStuff :state="state" :send="send"/>
</template>
<script>
import { myMachine } from './states'
import { useMachine } from "#xstate/vue"
export default {
setup(){
const { state, send } = useMachine(myMachine)
return {state, send}
}
}
</script>
And this worked fine in the beginning. But now that I have added asynchronous functions I ran into the following problem. The states in the different components get out of sync. While they are progressing as intended in the index page (going from 'loading' to 'otherState') they just get stuck in 'loading' in the other component. And not in a loop, they simply do not progress.
How can I make sure that the states are synced in all my components?
How do I solve this Vue Js error on Shopware 6 Administration. The module is suppose to select a column in the database table.
PS. This is the complete code. I'm trying to read data from the database and view it in the twig template.
const { Component, Mixin } = Shopware;
const { Criteria } = Shopware.Data;
import template from './store-settings-page.html.twig'
Component.register('store-settings-page', {
template,
inject: [
'repositoryFactory'
],
metaInfo() {
return {
title: this.$createTitle()
};
},
data: function () {
return {
entity: undefined,
storeData: null,
entityId: '4e2891496c4e4587a3a7efe587fc8c80',
secret_key: 'hdkkjjsmk538dncbjmns',
public_key: '1destinoDet2123lefmoddfk##$$%O',
}
},
computed: {
storeKeysRepository() {
return this.repositoryFactory.create('store_keys');
},
},
created() {
this.storeKeysRepository
.get(this.entityId, Shopware.Context.api)
.then(entity => {
this.entity = entity;
});
console.log(entity);
},
});
Apologies if my knowledge of Vue & JS is a bit off, based on how I see Shopware codes it, I recommend data to be written like this:
data() {
return {
...
};
}
I would also try to strip your file to the bear minimum to see when the error disappears.
Another thing to check is if you are running a JS file or TS file. Maybe it's having a hard time parsing your file because you are extending store-settings-page and it assumes it should be TypeScript?
this.storeKeysRepository
.get(this.entityId, Shopware.Context.api)
.then(entity => {
this.entity = entity;
console.log(this.entity);
});
This will do the trick
In my vue/cli 4/vuex opening page I need to fill select input with default value from
vuex store. To fill select input I need have selection items read from db and I have a problem that watch is triggered
BEFORE I read data from db in mount event.
I do as :
watch: {
defaultAdSavedFilters: {
handler: function (value) {
console.log('WATCH defaultAdSavedFilters value::')
console.log(value)
if (!this.isEmpty(value.title)) {
this.filter_title = value.title
}
if (!this.isEmpty(value.category_id)) {
this.categoriesLabels.map((nexCategoriesLabel) => { // this.categoriesLabels IS EMPTY
if (nexCategoriesLabel.code === value.category_id) {
this.selection_filter_category_id = {code: value.category_id, label: nexCategoriesLabel.label};
}
});
}
}
}, //
}, // watch: {
mounted() {
retrieveAppDictionaries('ads_list', ['ads_per_page', 'categoriesLabels']); // REQUEST TO DB
bus.$on('appDictionariesRetrieved', (response) => {
if (response.request_key === 'ads_list') { // this is triggered AFTER watch
this.ads_per_page = response.ads_per_page
this.categoriesLabels = response.categoriesLabels
// this.$forceUpdate() // IF UNCOMMENT THAT DOES NOT HELP
Vue.$forceUpdate() // THAT DOES NOT HELP
}
})
this.loadAds(true)
}, // mounted() {
I found this Can you force Vue.js to reload/re-render?
branch and tried some decisions, like
Vue.$forceUpdate()
but that does not work.
If there is a right way to trigger watch defaultAdSavedFilters AFTER I read array from db ?
Modified BLOCK :
I use Vuex actions/mutations when I need to read / keep /use /update data of the logged user, like
defaultAdSavedFilters, which is defined as :
computed: {
defaultAdSavedFilters: function () {
return this.$store.getters.defaultAdSavedFilters
},
Data ads_per_page(used for pagionaion), categoriesLabels(used for selection input items) has nothing to do with
logged user, that is why I do not use vuex for them, and I use retrieveAppDictionaries method to read them from the db
and bus to listen to them, which is defined as :
import {bus} from '#/main'
Sure I have data( block :
export default {
data() {
return {
...
ads_per_page: 20,
categoriesLabels: [],
...
}
},
"vue": "^2.6.10",
"vue-router": "^3.1.3",
"vuex": "^3.1.2"
Thanks!
Please add the data() method from you component. But I'm pretty sure it is NOT triggering because of the way you are assigning the result from the API call.
Try this:
mounted() {
retrieveAppDictionaries('ads_list', ['ads_per_page', 'categoriesLabels']); // REQUEST TO DB
bus.$on('appDictionariesRetrieved', (response) => {
if (response.request_key === 'ads_list') { // this is triggered AFTER watch
this.ads_per_page = [ ...response.ads_per_page ]
this.categoriesLabels = [ ...response.categoriesLabels ]
}
})
this.loadAds(true)
}
However, I don't understand what bus is doing for you and why you are NOT using Vuex actions/mutations
I created a Vue component which exports an async function. This component acts as a wrapper for calling my API. It's based on axios with a caching component that relies on localforage for some short lived persistence.
import localforage from 'localforage'
import memoryDriver from 'localforage-memoryStorageDriver'
import { setup } from 'axios-cache-adapter'
export default {
async cache() {
// Register the custom `memoryDriver` to `localforage`
await localforage.defineDriver(memoryDriver)
// Create `localforage` instance
const store = localforage.createInstance({
// List of drivers used
driver: [
localforage.INDEXEDDB,
localforage.LOCALSTORAGE,
memoryDriver._driver
],
// Prefix all storage keys to prevent conflicts
name: 'tgi-cache'
})
// Create `axios` instance with pre-configured `axios-cache-adapter` using a `localforage` store
return setup({
// `axios` options
baseURL: 'https://my.api',
cache: {
maxAge: 2 * 60 * 1000, // set cache time to 2 minutes
exclude: { query: false }, // cache requests with query parameters
store // pass `localforage` store to `axios-cache-adapter`
}
})
}
}
Here is how I am importing and using this component in my views:
import api from '#/components/Api.vue'
export default {
data() {
return {
userId: this.$route.params.id,
userData: ''
}
},
methods: {
loadClient(userId) {
const thisIns = this;
api.cache().then(async (api) => {
const response = await api.get('/client/find?id='+userId)
thisIns.userData = response.data.data[0]
}).catch(function (error) {
console.log(error)
})
},
},
created() {
this.loadClient(this.userId)
},
}
I can import this component and everything appears to work. I get data back from my API. However, immediately after every call, I get an error:
TypeError: Cannot read property 'cache' of undefined
Which references this line:
api.cache().then(async (api) => {
I am unable to understand why this is happening, or what it means. The error itself indicates that the component I am importing is undefined, though that's clearly not the case; if it were, the API call would ultimately fail I would suspect. Instead, I am lead to believe that perhaps I am not constructing/exporting my async cache() function properly.
Upon further review, I don't actually understand why the author has implemented it the way he has. Why would you want to create an instance of localForage every single time you make an API call?
I've opted not to use a component and to only instantiate an instance of localForage once.
main.js
import localforage from 'localforage'
import memoryDriver from 'localforage-memoryStorageDriver'
import { setup } from 'axios-cache-adapter'
// Register the custom `memoryDriver` to `localforage`
localforage.defineDriver(memoryDriver)
// Create `localforage` instance
const localforageStore = localforage.createInstance({
// List of drivers used
driver: [
localforage.INDEXEDDB,
localforage.LOCALSTORAGE,
memoryDriver._driver
],
// Prefix all storage keys to prevent conflicts
name: 'my-cache'
})
Vue.prototype.$http = setup({
baseURL: 'https://my.api',
cache: {
maxAge: 2 * 60 * 1000, // set cache time to 2 minutes
exclude: { query: false }, // cache requests with query parameters
localforageStore // pass `localforage` store to `axios-cache-adapter`
}
})
the view
export default {
data() {
return {
userId: this.$route.params.id,
userData: ''
}
},
methods: {
loadClient(userId) {
const thisIns = this;
thisIns.$http.get('/client/find?id='+userId)
.then(async (response) => {
thisIns.userData = response.data.data[0]
})
.catch(function (error) {
console.log(error)
})
},
},
created() {
this.loadClient(this.userId)
},
}
I am fetching the data from a MongoDB through sending GET requests to my API. Then I loop through the response.data and in each response.data through its properties to push the data which I need to nextArray. And this nextArray should be passed to the schedulingQuality-state in the Vuex. That's how it looks like:
methods: {
...mapActions(
['setSchedulingQuality']
),
get_data() {
const nextArray = [];
for(let i in this.SelectedtValues) {
axios.get('http://127.0.0.1:5000/getexp/'+this.SelectedtValues[i])
.then(res => {
for(let n in res.data) {
nextArray.push(res.data[n].output)
}
}
)}
console.log(nextArray);
},
computed: {
...mapGetters(
['schedulingQuality','selectedValues']
),
SelectedtValues() {
return this.$store.getters.selectedValues;
} ,
schedulingQuality() {
return this.schedulingQuality;
}
}
When I'm printing out the nextArray then it seems to be ok. I'm getting a [] on the console and after I click on it the correct content appears with a small i icon which tells: "Value below was evaluated just now". However I am not able to print out the items of this Array separately, each of them has a value of undefined, when I try that.
But my main problem is that it throws an Maximum call stack size exceeded error, when I'm trying to pass it to my Vuex-state in the code above befor printing out, like:
this.setSchedulingQuality(nextArray)
Here is my Vuex-code:
import Vuex from "vuex";
import axios from "axios";
const createStore = () => {
return new Vuex.Store({
state: {
schedulingQuality: [],
},
mutations: {
SchedulingQuality(state, payload) {
state.schedulingQuality = payload;
}
},
actions: {
setSchedulingQuality({commit}, payload){
commit('SchedulingQuality',payload)
}
},
getters: {
schedulingQuality(state) {
return state.schedulingQuality;
}
}
});
};
export default createStore;
My questions are:
Why it is not possible to print out the Array items separately?
Why I'am getting this error
And how can I fix it?
Thank you for your time.
axios call is asynchronous. At the time you call console.log(nextArray), axios function is not finished yet. That's why you got empty array.
You call multiple api asynchronously, I suggest you check out Promise.all
get_data() {
const nextArray = [];
Promise.all(this.SelectedtValues.map(value => {
return axios.get('http://127.0.0.1:5000/getexp/' + value)
})).then(results => {
results.map(res => {
for(let n in res.data) {
nextArray.push(res.data[n].output)
}
})
console.log(nextArray);
}).catch(err => {
console.error(err)
})
}