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

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.

Related

Can I use watch from Vue 3 with primitives?

Every time I fetch data, I want to change boolean value to render <Loading /> component.
I don't want my condition to be dependant on array length. So I decided to do it this way.
And <Loading /> component never reacts to state.isLoading change.
I tried to test whether this.isLoading changes at all using watch. But watch never logged anything.
I've never seen anybody using watch with primitives.
The problem is that I don't know if I can use watch with primitives and what I can use instead, like useEffect in React.
App.vue
<script setup>
import { RouterView } from 'vue-router'
import { watch, ref, onMounted, reactive } from 'vue';
import Navbar from './components/Navbar.vue'
import { useShopStore } from './stores/shopStore';
const shop = useShopStore()
const bool = ref(shop.isLoading)
console.log(bool)
watch(bool.value, (newBool) => {
console.log(newBool)
}, { deep: true })
</script>
Category.vue
<template>
<LoadingVue v-if="shop.isLoading" />
<div v-else class="category__menu">
<CardVue
v-for="item in shop.category"
:item="item"
:key="item.id"
/>
</div>
</template>
ShopStore.js
actions: {
async getProducts(path) {
if (typeof path !== 'string' || path === undefined) return
this.setLoading()
try {
const response = fetch(`https://fakestoreapi.com/products/category/${path}`)
.then(res => res.json())
.then(res => this.category = res)
} catch (error) {
console.log(error)
alert('Something went wrong')
}
this.setLoading()
},
setLoading() {
console.log('setLoading')
this.isLoading = !this.isLoading
}
}
You are creating a new ref over a reactive data. It's like copying by value, the original reactive data and the new ref wrapped over it are not connected. So when shop.isLoading changes, your bool ref doesn't, they are two different variables now.
I guess you are using pinia for the store. If so, the shop.isLoading is already reactive, you don't have to wrap it into a ref.
<Loading v-model="shop.isLoading" />
You can also use storeToRefs helper method from pinia to use destructuring over your store and get refs of your state:
const { isLoading } = storeToRefs(shop)
console.log(isLoading.value)
So.
The problem was that I used async but I didn't use await inside the function and that's why condition worked the way it worked. Or didn't work as I expected.
Now I fixed it and I want to publicly admit that I am a complete moron.
Thank you for your attention.
P.S.
Still didn't figure out how to use watch. The only way is to watch the whole state object. watch doesn't react to only state.bool value change.

Modify child component prop/data in parent's event handler

I am using vue.js 2 and ant design. I encountered such an issue when trying to change the loading state of a button in a modal, which is in an embedded component. Here is a similar example:
Code of modal component:
Modal.vue
<template>
<a-button :loading="loading" #click="onSubmit">Submit</a-button>
</template>
<script>
export default {
data(){
return {
loading: false
}
},
methods: {
onSubmit(){
this.$emit('submit')
}
}
}
</script>
Index.vue
<modal
#submit="submit" />
<script>
export default {
methods: {
submit(){
await axios.create(blabla...) //some async api call
}
}
}
</script>
Two failed approach:
Set the value of loading before and after $emit in onSubmit. It won't wait for the async request to finish.
Make loading a prop of Modal component and change it in the event handler. Nothing happens.
The only workable I have found so far is to create a method like setLoading in child component, pass that to the event handler as a parameter and call it. I was wondering if there is a more succinct way and what is the mechanism behind these.
you should pass the loading state as props. and add async before submitting the method. don't know why you mention failed cases. hard to tell without looking in that code. but try again with this code. maybe there are errors. so wrap in a try-catch block when you fetch details.
<modal
#submit="submit" :loading="loading" />
<script>
export default {
data(){
return {
loading: false,
}
}
methods: {
async submit(){ // add async keyword
this.loading = true
try{ // use try-catch block. see errors if there are any.
await axios.create(blabla...) //some async api call
}
catch(error){
console.log(error.response)
}
this.loading = false
}
}
}
</script>
Modal.vue
<template>
<a-button :loading="loading" #click="onSubmit">Submit</a-button>
</template>
<script>
props: {
loading: Boolean
}
</script>

Call a function from another component using composition API

Below is a code for a header and a body (different components). How do you call the continue function of the component 2 and pass a parameter when you are inside component 1, using composition API way...
Component 2:
export default {
setup() {
const continue = (parameter1) => {
// do stuff here
}
return {
continue
}
}
}
One way to solve this is to use events for parent-to-child communication, combined with template refs, from which the child method can be directly called.
In ComponentB.vue, emit an event (e.g., named continue-event) that the parent can listen to. We use a button-click to trigger the event for this example:
<!-- ComponentB.vue -->
<script>
export default {
emits: ['continue-event'],
}
</script>
<template>
<h2>Component B</h2>
<button #click="$emit('continue-event', 'hi')">Trigger continue event</button>
</template>
In the parent, use a template ref on ComponentA.vue to get a reference to it in JavaScript, and create a function (e.g., named myCompContinue) to call the child component's continueFn directly.
<!-- Parent.vue -->
<script>
import { ref } from 'vue'
export default {
⋮
setup() {
const myComp = ref()
const myCompContinue = () => myComp.value.continueFn('hello from B')
return {
myComp,
myCompContinue,
}
},
}
</script>
<template>
<ComponentA ref="myComp" />
⋮
</template>
To link the two components in the parent, use the v-on directive (or # shorthand) to set myCompContinue as the event handler for ComponentB.vue's continue-event, emitted in step 1:
<template>
⋮
<ComponentB #continue-event="myCompContinue" />
</template>
demo
Note: Components written with the Options API (as you are using in the question) by default have their methods and props exposed via template refs, but this is not true for components written with <script setup>. In that case, defineExpose would be needed to expose the desired methods.
It seems like composition API makes everything a lot harder to do with basically no or little benefit. I've recently been porting my app to composition API and it required complete re-architecture, loads of new code and complexity. I really don't get it, seems just like a massive waste of time. Does anyone really think this direction is good ?
Here is how I solved it with script setup syntax:
Parent:
<script setup>
import { ref } from 'vue';
const childComponent = ref(null);
const onSave = () => {
childComponent.value.saveThing();
};
</script>
<template>
<div>
<ChildComponent ref="childComponent" />
<SomeOtherComponent
#save-thing="onSave"
/>
</div>
</template>
ChildComponent:
<script setup>
const saveThing = () => {
// do stuff
};
defineExpose({
saveThing,
});
</script>
It doesn't work without defineExpose. Besides that, the only trick is to create a ref on the component in which you are trying to call a function.
In the above code, it doesn't work to do #save-thing="childComponent.saveThing", and it appears the reason is that the ref is null when the component initially mounts.

Vue JS best approach to wait for value in mounted hook

In my Vue component I want to fetch work orders via axios. To do that, I need a user id, which is loaded via a computed property.
Just calling my method 'loadWorkorders' in the mounted lifecycle hook, results in the computed property not being available yet. So I want to check if this computed property is available, if not, just wait a few milliseconds and try again. Repeat the same cycle until it is available.
What is the best approach to handle this? Now I'm just waiting for a second and this seems to work, but I'm looking for a more robust approach.
My code:
export default {
name: "MyWorkorders",
mounted() {
if (this.user) {
this.loadWorkorders();
} else {
setTimeout(this.loadWorkorders(), 1000);
}
},
data() {
return {
workOrders: null,
};
},
methods: {
loadWorkorders() {
axios
.get(`/workorders/load-user-workorders/${this.user.id}`)
.then((res) => {
this.workOrders = res.data.workorders;
})
.catch((err) => {
console.log(err);
});
},
},
computed: {
user() {
return this.$store.getters.getUser;
},
},
};
If you are using Vue 3, consider using Async Components, which are also related to the Suspense built-in component. (at the time of this post, Suspense is an experimental feature, however Async Components are stable)
If you are using Vue 2, take a look at this part of the documentation about handling loading state in Async Components.
If you do not want to invest in these solutions because your use case is very, very simple -- you can try the following simple solution:
<template>
<template v-if="loading">
loading...
</template>
<template v-else-if="loaded">
<!-- your content here -->
</template>
<template v-else-if="error">
error
</template>
</template>
I have made this runnable example for demonstration. Of course, the templates for loading and error states can be made more complex if required.

VUE add a component with JS

Can I create/mount a VUE component calling a JS function in a fullyloaded page?
As a async call? Something like:
function getComponent(obj){
return <component parameters="obj"></component >;
}
Can I create/mount a VUE component calling a JS function in a fullyloaded page?
I think you want to load/mount the component in a certain condition and not on page initialization.
If that the case then you can take advantage of lazy-loading and dynamic components:
<template>
//...
<button #click="activateComponent">Activate component</button>
<component :is="dynamicComponent" />
//...
</template>
<script>
export default {
components: {
MyCmp: () => import('./MyCmp.vue') //lazy loading
},
data: () => ({
dynamicComponent: null
}),
methods: {
activateComponent () {
this.dynamicComponent = 'MyCmp'
}
}
}
</script>
I think what you are looking for is Vue dynamic components. What that allows you is to choose which component to load after the original component has loaded. You can do that whenever and however you want to, aka in an async way. You can read more in the Vue guides. All the code you need is there so won't repost it here again.