Vue trigger mounted before async created completed - vue.js

VueJS fires mounted before async created done.
The base html is rendered by SSR (Laravel/PHP) for SEO and users. So, it is desirable to complete this.fetchCakeShops() and variable this.cakeShops is filled before mount id="cake-shop-list" to virtual DOM. (Otherwise, the screen become blank until this.cakeShops is set) Therefore I wrote code like bellow.
<!-- Base html -->
<div id="cake-shop-list">
<!-- Already server side rendered cake shops list -->
<ul><li>shopA</li><li>shopB</li></lu>
</div>
<template>
<div>
<ul>
<li v-for="shop in cakeShops" :key="shop.id" :click="onClickShop(shop.id)">{{shop.name}}</li>
</ul>
</div>
</template>
<script>
export default {
...
async created() {
console.log("created start!")
this.cakeShops = await this.fetchCakeShops({page: 1})
console.log("created ends!", this.cakeShops)
},
mounted() {
console.log("mounted!")
},
methods: {
...
}
}
</script>
I wish mounted hook starts after created. But the log console shows like bellow, and the DOM is override with blank virtual DOM in a few seconds.
created start!
mounted! <- Oops
created ends!
Array(10)[{id: 1, name: 'shopA'},{id: 2, 'shopB'}...]
Is there any solution to wait mount until async is complete?
note: I can not pass shops array to props.

The mounted method will not wait untill the created method is done (event if its async) instead what you could do is create a different function and call this at the end of your create function
this will look something like this:
async created() {
this.cakeShops = await this.fetchCakeShops({page: 1})
this.loaded();
},
methods: {
loaded: {
//code here
},
}

Related

how to re-mount a child component in vuejs

im searching how to re-mount a child component. Not just re-render it, but totally re-mount (so mounted() function of my child component will be called.
I know there already is a similar question (How to re-mount a component in VueJS?), but the author was searching how to re-rendre (and the solution match this case), when i follow these instructions, my mounted() function is not called.
here is what i have:
I have a page with 2 component:
an upload component
a list component
here is what i want:
I use my upload component to upload a .zip file to my api. Then, the API parse the .zip, and store its information in database.
My list il a table of every .zip, displaying basic informations (name, description...), taking data from the store, and if empty, using axios to fetch from API (everything into the mounted() hook).
the only solution i have to update the list when i upload a new .zip is to fetch the list from my API.
here is the problem: i cant find a way to update my list. Knowing data are fetched when my list component is mounted, the solution would be to re-mount my component, But I can't find how, so I'm asking you guys.
here is my page code:
<template>
<div>
<div class="col-md-4 text-center mr-auto">
<card>
<Upload #upload="upload" ></Upload>
</card>
</div>
<div>
<List :ProjID="selectedProject.id" :key="childKey"/>
</div>
</div>
</template>
<script>
import upload from "./upload.vue";
import API from "./services/API";
import list from "./list.vue";
export default {
name: "ProjectCard",
components: {
Upload,
List,
},
props: {
selectedProject: null
},
data() {
return {
childKey: 0,
};
},
methods: {
upload(file) {
API.postJob(file, this.selectedProject.id)// <-- function calling axios, everything is working here. It also delete stored file in my store, so the list component will fetch from API to get new data
.then(response => {
this.childKey += 1;
console.log("success!")
})
.catch(response => {
console.log("not succes :c")
});
}
}
};
</script>
here is my list mounted() hook:
async mounted() {
const fetch = await API.getAll(); <-- this function fetch from my store, and if nothing comes out, fetch from API.
if (fetch == 401) {
console.log("not logged in");
}
if (fetch == 500) {
console.log("error");
return();
}
this.fetchedFiles = fetch;
this.dataReady = true;
}
I have to re-call the mounted() hook in order to update my list (or any better solution)

Nuxt async fetch() creating multiple instances? Repeated fetch() calls

I have a simple BasePreviewImage component that needs to fetch an Array.Buffer() asynchronously from an internal API. However, it appears that async fetch() is called for every instance ever created despite the components themselves being destroyed.
Example:
<template>
<div class="image-container">
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'nuxt-property-decorator'
#Component({})
export default class BasePreviewImage extends Vue {
#Prop({ type: String }) id: string
#Prop({ type: String, default: 'small' }) size: string
image: string = ''
async fetch() {
console.log('async fetch', this)
}
created() {
console.log('created')
}
mounted() {
console.log('mounted')
}
beforeDestroy() {
console.log('beforeDestroy')
}
}
</script>
Output when I load a page with 1 BasePreviewImage component then back, then re-open the page. This continues calling fetch n times the page has been opened.
How do I avoid making the API call multiple times as a user navigates pages and is there some other memory leak going on here?
I'm not really sure if the problem is code, config, vue, nuxt, nuxt-property-decorator, vue-class-component, or somewhere else.
Related but not helpful: https://github.com/vuejs/vue-class-component/issues/418

Call API automatically to fetch data with prop value when component is displayed in Vue.js

I have a page which displays a list of mutual funds. With each mutual fund, I have a button to display their NAV history. This button calls a component which has an embedded API call to fetch the NAV history. I pass the fund code for which the data is to be fetched as a prop to the component. However, I am not able to trigger the API call automatically when the prop is called.
this is my code as of now:
Parent component (main page):
<template>
<!-- some code -->
<a href="#" #click="fetchNavHistory(fund)">
<v-icon small>history</v-icon>
</a>
<!-- some more code -->
<NAVHistory
:show="showNavHistory"
:amfi_code="amfi_code"
ref="history"
#hide="showNavHistory=false"
/>
</template>
export default {
name: "FundList",
components: {
NAVHistory
},
data() {
return {
showNavHistory: false,
amfi_code: 0
}
},
methods:{
fetchNavHistory(fund){
this.amfi_code = fund.amfi_code
this.showNavHistory = true
var child = this.$refs.history
child.fetchNavHistory()
}
}
}
Child component (where NAV history is displayed):
<template>
<!-- some code -->
</template>
<script>
export default {
props: {
show: Boolean,
amfi_code: Number
},
data(){
return{
apiURL: process.env.VUE_APP_BASEURL,
navHistory: [],
}
},
methods: {
async fetchNavHistory(){
try{
const response = await fetch(this.apiURL + '/navhistory', {
method: 'POST',
body: JSON.stringify({"amfi_code": this.amfi_code}),
headers: {'content-type': 'application/json; charset=UTF-8'},
})
const data = await response.json()
console.log(data)
this.navHistory = data
} catch(error){
console.log(error)
}
}
}
}
</script>
At first I tried calling the fetchNavHistory() method on updated() event. But that kept calling the API non-stop when the component was displayed on the screen.
Then I tried adding a watch for the show prop. But that didn't work at all.
Finally, as a workaround, I called the API from the parent component itself. While that is working, it is calling the component with the previous value of the amfi_code, rather than the updated value. So the first time it gets called, the amfi_code is passed as 0.
Is there a way to safely trigger the API call when the component is displayed, i.e., the show prop is set to true?
You can try watch with deep:true option that way the watch will be triggered when a component will be mounted. Or you can call API on mounted hook and check show prop in it.
deep:true means a watch will look at if changes occur not only for a watched prop but additionally at all nested props.
immediate:true means that a watch will fire after a component is mounted (when a watched prop has initial value).

Is `async/await` available in Vue.js `mounted`?

I'd like to do something like this in mounted() {}:
await fetchData1();
await fetchData2UsingData1();
doSomethingUsingData1And2();
So I wonder if this works:
async mounted() {
await fetchData1();
await fetchData2UsingData1();
doSomethingUsingData1And2();
},
In my environment it raises no errors, and seems to work well.
But in this issue, async/await in lifecycle hooks is not implemented.
https://github.com/vuejs/vue/issues/7209
I could not find further information, but is it available in fact?
It will work because the mounted hook gets called after the component was already mounted, in other words it won't wait for the promises to solve before rendering. The only thing is that you will have an "empty" component until the promises solve.
If what you need is the component to not be rendered until data is ready, you'll need a flag in your data that works along with a v-if to render the component when everything is ready:
// in your template
<div v-if="dataReady">
// your html code
</div>
// inside your script
data () {
return {
dataReady: false,
// other data
}
},
async mounted() {
await fetchData1();
await fetchData2UsingData1();
doSomethingUsingData1And2();
this.dataReady = true;
},
Edit: As stated in the documentation, this is an experimental feature and should not be used in production applications for now.
The correct way to do this in vue3 would be to make your setup() function async like this:
<script>
// MyComponent.vue
export default defineComponent({
/* ... */
async setup() {
await fetchData1();
await fetchData2UsingData1();
doSomethingUsingData1And2();
this.dataReady = true;
}
}
</script>
And then use a suspense component in the parent to add a fallback like this:
<template>
<Suspense>
<template #default>
<MyComponent />
</template>
<template #fallback>
Loading...
</template>
</Suspense>
</template>
So you would see the #fallback template while the component is loading, and then the component itself when it's ready.
Just use $nextTick to call async functions.

How to defer passing of Ajax data to child components?

I'm building a simple to-do app with Vue 2.0 using built-in parent-child communication. The parent element that is directly attached to the Vue instance (new Vue({...}) is as follows:
<!-- Tasks.vue -->
<template>
<div>
<create-task-form></create-task-form>
<task-list :tasks="tasks"></task-list>
<task-list :tasks="incompleteTasks"></task-list>
<task-list :tasks="completeTasks"></task-list>
</template>
<script>
import CreateTaskForm from './CreateTaskForm.vue';
import TaskList from './TaskList.vue';
export default {
components: { CreateTaskForm, TaskList },
data() { return { tasks: [] }; },
created() { // Ajax call happens here...
axios.get('/api/v1/tasks')
.then(response => {
this.tasks = response.data;
console.log(this.tasks); // THIS IS LOGGED LAST
});
},
computed: {
completeTasks() {
return this.tasks.filter(task => task.complete);
},
incompleteTasks() {
return this.tasks.filter(task => task.complete);
}
}
}
</script>
The idea is that <tasks></tasks> will display a form to create a new task, as well 3 lists - all tasks, incomplete, and complete tasks. Each list is the same component:
<!-- TaskList.vue -->
<template>
<ul>
<li v-for="task in taskList">
<input type="checkbox" v-model="task.complete"> {{ task.name }}
</li>
</ul>
</template>
<script>
export default {
data() { return { taskList: [] }; },
props: ['tasks'],
mounted() {
this.taskList = this.tasks;
console.log(this.tasks); // THIS IS LOGGED FIRST
}
}
</script>
Problem
As you can see, I am trying to pass data from <tasks> to each of the 3 <task-lists>, using dynamic :tasks property:
<task-list :tasks="tasks"></task-list>
<task-list :tasks="incompleteTasks"></task-list>
<task-list :tasks="completeTasks"></task-list>
Note, I am not using shared (global) state, because each list needs a different portion of data, even though these portions of data belong to the same store. But the problem is that :tasks are assigned an empty array before the Ajax call happens; and as I am guessing, props are immutable, hence tasks in the child <task-list> are never updated when data is fetched in the parent <tasks>. In fact, <task-list> is created first (see the log), and only then the data is fetched using Ajax.
Questions
How do I defer the passing of data from <tasks> to my <task-list>s? How do I make sure that all components refer to the single source of truth that's updated dynamically?
Is there a way to solve this parent-child communication problem with "vanilla" Vue.js? Or do I need to use Vuex or something similar?
Am I right in using properties to pass data to children? Or should I use shared store in a global variable?
The problem is here: mounted() { this.taskList = this.tasks; ...} inside TaskList.vue as taskList property is updated only on mounted event.
There's a trivial solution in Vue: you should make taskList a computed property which depends on props, so that when parent data changes your computed property gets updated:
props: ['tasks'],
computed: {
taskList: function() {
return this.tasks;
}
}
Don't forget to remove taskList from data block.
Also, I would rewrite v-model="task.complete" into #change="$emit('something', task.id)" to inform a parent component that status has changed (and listen to ). Otherwise parent will never know the box is checked. You can then listen for this event on parent component to change tasks status accordingly. More reading: https://v2.vuejs.org/v2/guide/components.html#Custom-Events
You can also use watch property of vue instance
watch:{
tasks(newVal){
this.taskList = newVal;
//do something
}
}