Vue Vuetify async function inside data-table template - vue.js

I am trying to populate a column with data I get from my API. By doing it the way tried, the column only shows "[object Promise]" instead of the array of objects I get back from the api.
What am I doing wrong?
<template v-slot:item.holdings="{ item }">
{{ getTokenBalance(item) }}
</template>
async getTokenBalance(item) {
try {
let address = this.getSyncedAddress(item);
const query = new Moralis.Query("TokenBalance");
query.equalTo("address", address);
query.descending("updatedAt");
const balance = await query.first();
return balance.attributes.balance;
} catch (error) {
console.log(error);
}
},

An async function automatically wraps it inside a Promise.
So what your function is returning is not a string but a Promise<string>.
On the template, when you print a non primitive value, it will convert it to string using toString(). For a Promise, this give [object Promise].
A possible solution would be to populate your item object directly, instead of returning the result, everytime the displayed items change:
<template>
<v-data-table #current-items="populateItems">
<template v-slot:item.holdings="{ item }">
{{ item.balance }}
</template>
</v-data-table>
</template>
<script>
populateItems(displayedItems)
// Load the balance for all displayed items (to avoid requesting all items, even the onces not displayed)
return Promise.all(diplayItems.map(item => this.getTokenBalance(item)))
}
async getTokenBalance(item) {
try {
// [...]
item.balance = balance.attributes.balance;
} catch (error) {
console.log(error);
}
}
</script>
Note: Maybe you'll need to call populateItems once at loading, I don't know if the current-items event is fired on mounting.

Related

Problem with rendering data and heavy data loading

The first problem is that when getDetails(‘multiple’, ‘2’) function is called inside HTML, it takes some time until de data is displayed from v-for.
The second problem is that when I call the console.log(userDetails) from inside of anotherFunction() I got the undefined answer. It doesn’t wait for the this.getDetails(‘multiple’, ‘1’) to execute completely.
How can I improve the time for rendering, or should I use another way to display de data?
How can I make the second function to wait until the first function is complete?
VUE version: 2.7.10
<div id="app">
<p v-for="item in userDetails">item is displayed</p> //
<button #click="anotherFunction()">Click Me!</button>
</div>
<script>
export default {
name: 'App',
data: {
userDetails: []
}
}
// axios function
getDetails(actionType, idUser) {
axios.post("https://example.com/link", {
Username: username
}).then(response => {
const result = response.data;
// push data into variable
this.userDetails.push(result[0])
}).catch(error => {
this.showError('Error', 4000);
console.error('Error:' + error);
});
// another function from where I want to call the axios function
anotherFunction() {
this.getDetails('multiple', '1')
// call the userDetails into another function will output "undefined"
console.log(this.userDetails);
}

Vue 3 display fetch data v-for

So, I'm creating a Pokemon application and I would like to display the pokemon names using the api : https://pokeapi.co/api/v2/pokemon/.
I'm doing a fetch request on the api and then display the pokemon names in my template. I have 0 problem when I try to display only 1 pokemon but I have this error when I try to display all my pokemons using v-for.
Do you have any idea why I meet this error ?
<template>
<p class="dark:text-white"> {{pokemons[0].name}} </p> //working
<div v-for="(pokemon, index) in pokemons" :key="'poke'+index"> //not working...
{{ pokemon.name }}
</div>
</template>
<script>
const apiURL = "https://pokeapi.co/api/v2/pokemon/"
export default {
data(){
return{
nextURL:"",
pokemons: [],
};
},
created(){
this.fetchPokemons();
},
methods:{
fetchPokemons(){
fetch(apiURL)
.then( (resp) => {
if(resp.status === 200){
return resp.json();
}
})
.then( (data) => {
console.log(data.results)
// data.results.forEach(pokemon => {
// this.pokemons.push(pokemon)
// });
// this.nextURL = data.next;
this.pokemons = data.results;
console.log(this.pokemons);
})
.catch( (error) => {
console.log(error);
})
}
}
}
</script>
<style>
</style>
I've just pasted your code into a Code Pen and removed the working/not working comments and the code runs and shows the names.
Maybe the problem is in the parent component where this component is mounted, or the assignment of the :key attribute
try :key="'poke'+index.toString()", but I'm pretty sure js handels string integer concats quiet well.
Which version of vuejs do you use?
Edit from comments:
The parent component with the name PokemonListVue imported the posted component as PokemonListVue which resulted in a naming conflict. Renaming either one of those solves the issue.
In the error message posted, in line 3 it says at formatComponentName this is a good hint.

vue.js - Data doesn't show in console.log but renders fine in template

I have a Vue template in which I make async/await calls to get all sessions data, this data does not have to be rendered, but sent to another component in the form of an array, and the other component will get that info and produce some graphs. As a test, I added the array sessionSelected to the html template to see if it loads correctly and works just fine (This data change is triggered by a select component when selecting a program).
The behavior that I'm confused with however can be seen in the listSessions() method below, where I have console.log(val) that is inside a map for the sessionSelected array iteration;
When I check the console, the object that is being returned there is blank the first time I choose an option from the select component (a program), but when I pick another option, let's say program 6 it loads the previous sessions in the console.log(val), even though the same data object, when iterated through in the template, is displaying all the sessions correctly . (It's kinda like it always go, one "tick" behind)
A possible hint, if it helps, I added an #click to a <p> element below the select's components, so when the program is chosen, say program 2, and then I click to that <p> tag, the console.log does show correctly from the "listSessions" method.
I need to be able to have the sessionSelected array object synced, in such a way so that I'm sure that when I select a program, in the html template, the method will retrieve the right array (of sessions) like shows rendered in template.
<template>
<v-container>
<v-layout>
<v-flex lg4 sm12 xs12>
<GPSelect #input="listTreatments" v-model="patientSelected" :items="tvPatients" label="Patients" />
</v-flex>
<v-flex lg4 sm12 xs12>
<GPSelect #input="listPrograms" v-model="treatmentSelected" :items="treatments" label="Treatments" :disabled="treatments === undefined || treatments.length === 0" />
</v-flex>
<v-flex lg4 sm12 xs12>
<GPSelect #input="listSessions" v-model="programSelected" :items="programs" label="Programs" :disabled="programs === undefined || programs.length === 0" />
<p #click="listSessions">Session selected {{sessionSelected}}</p>
<p>ProgramSelected {{programSelected}}</p>
</v-flex>
</v-layout>
<BarChart :label="label" :data="dataSet" :labels="labels" />
</v-container>
</template>
<script>
export default {
data() {
return {
tvPatients: [],
patientSelected: "",
treatments: [],
programs: [],
sessions: [],
treatmentSelected: "",
programSelected: "",
sessionSelected: [],
dataSet: [],
...
}
},
created() {
this.listPatients();
},
methods: {
async listSessions() {
await this.getSessions();
this.updateData();
this.sessionSelected.map(async (val) => {
console.log( val)
})
this.sessionSelected.length = 0;
this.sessions.length = 0;
},
async getSessions() {
if (this.patientSelected) {
const response = await SessionService.getSessions(null, "meta");
if (response.data) {
return response.data.map(async (val, index) => {
if (val.program_id === this.programSelected) {
if (this.sessions != undefined) {
this.sessions.push(await SessionService.getSession(val._id, "meta"));
this.sessionSelected.push(await SessionService.getSession(val._id, "meta"));
}
}
})
}
}
},
async listPrograms() {
this.programs = await this.getPrograms();
},
async getPrograms() {
let response = await PatientService.getPatient(this.patientSelected, "tv");
if (this.patientSelected) {
const params = {
"treatment-id": response.data.documents[0].document.active_treatment_id
};
const programResponse = await ProgramService.getPrograms(params);
return await programResponse.data.map((val, index) => {
return {
name: `Program ${(index + 1) } ${response.data.documents[0].document.first_name}`,
value: val._id
}
});
}
}
}
}
</script>
I expect that the console.log(val) inside the map of the this.sessionSelected shows the same data displayed in the template, without having to use the <p> tag with the #click event as a hack, basically, that when a program gets selected from the select component, loads the associated data.
Quite difficult to follow with so much async/await going on. A bit of refactoring to deal with the pyramid of doom wouldn't hurt.
This line catches my eye:
return response.data.map(async (val, index) => {
This will be returning an array of unresolved promises. The surrounding function, getSessions, is async so it will wrap the return value in a further promise.
The line await this.getSessions(); will wait for that promise to resolve. Unfortunately it'll resolve immediately to the array of promises, without waiting for the individual promises to resolve. This is why the logging appears to be one step behind, as the inner promises haven't finished yet.
I think what you need is to add Promise.all, such that the outer promise waits for the array of promises.
return Promise.all(response.data.map(async (val, index) => {

Issue with method not returning values in interpolation nuxjs/vue

In my nuxtjs app static folder I have a file called data.json
in my component I use this data like so
<script>
import data from '~/static/data.json';
export default {
data ({ params }) {
return {
data
}
}
}
</script>
now I have a method that will basically take values from that data and create a little counting up animation like so
methods: {
countUp(value) {
for (let i = 0; i <= value; i++) {
setTimeout(() => {
return i;
}, 100);
}
}
}
and in my template I am calling it like so
<template>
<div>
<p>{{countUp(data.number)}}</p>
</div>
</template>
now the expected result is for the number to quickly change from 0 to the value but nothing is being printed on the dom if I inspect the html element its empty??
What am I doing wrong??
setTimeout doesn't work the way you think it does:
You can't return a value from inside the callback function; nothing is being returned from the countUp method.
The call to setTimeout doesn't block, meaning it will return immediately after being called and the callback function passed to it is scheduled for execution asynchronously after the timeout has passed. So every setTimeout call in the for loop will be executed all at once after 100 ms instead of staggered.
You will need to store the current counter value as data on the component so Vue knows to rerender when its value is changed.
The simplest example I can provide follows, but you might want to encapsulate the logic in a separate reusable component.
const value = 50
new Vue({
el: '#app',
data: {
counter: 0,
},
methods: {
countUp() {
const interval = setInterval(() => {
this.counter++
if (this.counter >= value) {
clearInterval(interval)
}
}, 100)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="countUp">Count Up</button>
{{ counter }}
</div>

Push not updating array in DOM Vue

I am using Vue and am trying to make live search. But on updating the content of search, it doesn't get updated.
Data do get update in array, when checked in dev tools. But DOM don't get updated.
template
<div class="dropdown">
<input type="text" v-model="input" placeholder="Search" #keyup="searching" data-toggle="dropdown">
<span class="caret"></span>
<ul class="dropdown-menu">
<li v-for="(data,index) in availSearchData" :key="index">
{{data.name}}
</li>
</ul>
</div>
method
searching() {
if (this.input) {
let url = this.domain + "search";
axios
.get(url, {
params: {
table: this.table,
data: this.input
}
})
.then(res => {
this.availSearchData = [];
res.data.forEach(doc => {
this.availSearchData.push(doc);
});
});
}
}
I don't know where I am doing wrong.
Please help out if possible.
To add an item to the back of an array and get it to be reactive in Vue, below is what worked for me:
this.$set(this.items,
this.items.length,
JSON.parse(JSON.stringify(this.item))
);
The this.$set is Vue's inbuilt array manipulation function that guarantees reactivity.
The this.items is the array, this.items.length (NOTE: it is items.length NOT items.length - 1) is to push a new index to the back of the array and finally, JSON.parse(JSON.stringify(this.item)) is to clone the this.item into a new object before pushing into the array. The cloning part may not be applicable to you and I used this in variables because all the variables are declared in my data() function.
Use a computed property in your component and use that for parsing the template like this
<li v-for="(data,index) in availSearch" :key="index">
{{data.name}}
</li>
and computed property will be then
availSearch() {
return this.availSearchData;
},
so this computed property always return the array if it is updated.
Also if your response is the array that you want to use exactly, try this
searching() {
if (this.input) {
let url = this.domain + "search";
axios
.get(url, {
params: {
table: this.table,
data: this.input
}
})
.then(res => {
this.availSearchData = [];
Vue.set(this, 'availSearchData', res.data);
});
}
}
Possible explanations for this might be:
You don't declare the property in the component and thus normal
reactivity doesn't work.
You are using index as the key in your array. This might confuse the
reactivity system, so it does not necessarily know if the item
changed. Try using the name of the item as the key instead.
Try calling your function from mounted hook. I think the problem is that you are trying to show data when the DOM is not rendered yet. By calling your function in mounted you get data back after DOM has been rendered.
mounted() {
this.searching();
}
from Vue website "mounted: Called after the instance has been mounted, where el is replaced by the newly created vm.$el. If the root instance is mounted to an in-document element, vm.$el will also be in-document when mounted is called."