I'm sending 2 api requests before render the page:
const Profile = {
template: '#profile',
attributes: null,
photos: [],
data: function () {
return {attributes: Profile.attributes, photos: Profile.photos};
},
beforeRouteEnter: function (to, from, next) {
function getProfile() {
return axios.get('user/get-profile?access-token=1', {responseType: 'json'});
}
function getPhotos() {
return axios.get('photos?access-token=1', {responseType: 'json'});
}
axios.all([getProfile(), getPhotos()])
.then(axios.spread(function (profile, photos ) {
console.log(profile, photos );
next(vm => {
vm.setProfile(profile);
vm.setPhotos(photos);
})
}));
},
methods: {
setProfile: function (response) {
Profile.attributes = response.data;
console.log(Profile.attributes);
},
setPhotos: function (response) {
Profile.photos = response.data;
console.log(response);
},
}
};
The problem is rendering occures before setProfile
and setPhotos
methods. How to correct render my component?
Put the fetchData function above in the useEffect hook and call it, like so: useEffect(() => { const url = "https://api.adviceslip.com/advice"; const fetchData = async () => { try { const response = await fetch(url); const json = await response. json(); console. log(json); } catch (error) { console.
There are no correct ways to make API call before component rendered from the same component. You may preferred make API call in parent component and render presentation component when and only when have consumable data.
To wait for a ReactJS component to finish updating, we use a loading state in our react application by use of the conditional rendering of the component. This can be achieved by the use of the useState and useEffect hooks in the functional components.
So one solution is to use hooks as a separate component in your class. And then in your js class you have access to all lifecycle methods. There is a method shouldComponentUpdate(state, props) It takes props and states and you can compare if you want to re-render screen or not.It will call right before rendering.
As mentioned in the comments, the first async/await solution is a little bit misleading, because all lifecycle methods are synchronous. This works, because the code is transpiled to an synchronous function with an IIFE inside.
Below I have added a few more recent snippets.
Try it with async/await. I've removed beforeRouteEnter
, axios.spread
and added create
.
const Profile = {
template: '#profile',
attributes: null,
photos: [],
data() {
return {
attributes: null,
photos: null,
};
},
async created() {
const getProfile = await axios.get('user/get-profile?access-token=1');
const getPhotos = await axios.get('photos?access-token=1');
this.setProfile(profile);
this.setPhotos(photos);
},
methods: {
setProfile(response) {
this.attributes = response.data;
console.log(this.attributes);
},
setPhotos(response) {
this.photos = response.data;
console.log(response);
},
},
};
Shorter
const Profile = {
template: '#profile',
attributes: null,
photos: [],
data() {
return {
attributes: null,
photos: null,
};
},
async created() {
this.attributes = await axios.get('user/get-profile?access-token=1');
this.photo = await axios.get('photos?access-token=1');
},
};
You can use an async function inside your lifecycle method.
const Profile = {
template: '#profile',
attributes: null,
photos: [],
data() {
return {
attributes: null,
photos: null,
};
},
created() {
const fetchData = async () => {
const { data: attributes } = await axios.get(
'user/get-profile?access-token=1'
);
const { data: photos } = await axios.get('photos?access-token=1');
this.attributes = attributes;
this.photos = photos;
};
fetchData();
},
};
setup()
In Vue 3 you can use async setup()
. If you use this, you must wrap your component wit Suspense
. Caution! This API is currently experimental https://v3.vuejs.org/guide/migration/suspense.html.
<Suspense>
<template #default>
<YourComponent />
</template>
<template #fallback>
<div>Loading ...</div>
</template>
</Suspense>
export default {
name: 'YourComponent',
async setup() {
const { data: attributes } = await axios.get('user/get-profile?access-token=1');
const { data: photos } = await axios.get('photos?access-token=1');
return {
attributes,
photos
}
}
}
You should just be able to return the Promise that is returned from calling axios.all like:
return axios.all([getProfile(), getPhotos()])
// .then() => ...
Or you could add a property to the data object and use this to show a loader until all Promises have resolved
const Profile = {
template: '#profile',
attributes: null,
photos: [],
data: function () {
return {attributes: Profile.attributes, photos: Profile.photos, isLoading: true};
},
beforeRouteEnter: function (to, from, next) {
function getProfile() {
return axios.get('user/get-profile?access-token=1', {responseType: 'json'});
}
function getPhotos() {
return axios.get('photos?access-token=1', {responseType: 'json'});
}
axios.all([getProfile(), getPhotos()])
.then(axios.spread(function (profile, memes) {
console.log(profile, memes);
this.isLoading = false
next(vm => {
vm.setProfile(profile);
vm.setPhotos(photos);
})
}));
},
methods: {
setProfile: function (response) {
Profile.attributes = response.data;
console.log(Profile.attributes);
},
setPhotos: function (response) {
Profile.photos = response.data;
console.log(response);
},
}
};
Template code omitted, but you can just switch the content you display based on the isLoading. If you go down this route then probably best to create an abstraction for the loader.
I would also suggest you might want to look at vuex rather than coupling all of your data to any specific component state.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With