I want to make several API calls to get data into a component. I created a PostService.ts that looks like this:
const apiClient = axios.create({
baseURL: '/api/v1',
})
export default {
async getPosts() {
const { data }: { data: Post[] } = await apiClient.get('/posts')
// transform data ...
return data
},
async getTags() {
const { data }: { data: Tag[] } = await apiClient.get('/tags')
return data
},
async getComments() {
const { data }: { data: Comment[] } = await apiClient.get('/comments')
return data
},
}
This is my posts.vue:
<template>
<div>
<div v-if="dataLoaded">
content
</div>
<div v-else>
loading...
</div>
</div>
</template>
<script>
finishedApiCalls = 0
get dataLoaded() {
return this.finishedApiCalls === 3
}
created() {
PostService.getPosts()
.then((posts) => {
this.posts = posts
this.finishedApiCalls++
})
.catch((error) => {
console.log('There was an error:', error)
})
PostService.getTags()
.then((tags) => {
this.tags = tags
this.finishedApiCalls++
})
.catch((error) => {
console.log('There was an error:', error)
})
PostService.getComments()
.then((comments) => {
this.comments = comments
this.finishedApiCalls++
})
.catch((error) => {
console.log('There was an error:', error)
})
}
</script>
The key point is that I want to display a loading spinner as long as the data has not been loaded. Is it recommended to make the API calls from created()? What would be a more elegant way to find out when all calls are finished? It does not feel right to use the finishedApiCalls variable.
I recommend using Nuxt's fetch method along with Promise.all() on all your async PostService fetches:
// MyComponent.vue
export default {
fetch() {
return Promise.all([
PostService.getPosts().then((posts) => ...).catch((error) => ...),
PostService.getTags().then((tags) => ...).catch((error) => ...),
PostService.getComments().then((comments) => ...).catch((error) => ...)
])
}
}
Nuxt provides a $fetchState.pending prop that you could use for conditionally rendering a loader:
<template>
<div>
<Loading v-if="$fetchState.pending" />
<div v-else>My component data<div>
</div>
</template>
You can use Promise.all for this kind of requirements.
this.loading = true
Promise.all([PostService.getPosts(), PostService.getTags(), PostService.getComments()])
.then(values => {
let [posts, tags, comments] = values
this.posts = posts
this.tags = tags
this.comments = comments
//Here you can toggle your fetching flag like below
this.loading = false
})
You can use Promise.all(). This will wait till all resolves or if 1 fails.
With async / await you can make it "synchronous"
data() {
return {
loaded: false
}
},
async created() {
let [posts, tags, comments] = await Promise.all([PostService.getPosts(), PostService.getTags(), PostService.getComments()])
this.posts = posts;
this.tags = tags;
this.comments = comments;
this.loaded = true;
}
Related
this my code previously
<ul>
<li v-for="item in data.data">{{ item.name }}</li>
</ul>
export default {
layout: 'main',
async asyncData({ $axios }) {
const data = await $axios.$get('XXXXXXXXXXXXXXXXXXXXX');
return { data };
}
}
this can work but when i change the page and turnback to this page
axios send request again i need one time request and save data response to cache (localStorage)
now i try this code
export default {
layout: 'main',
mounted() {
this.starter()
},
methods: {
starter() {
let room = JSON.parse(localStorage.getItem('game_room'))
if (!room) {
fetch('XXXXXXXXXXXXXXXXXXXXX')
.then(res => res.json())
.then(json => {
localStorage.setItem('game_room', JSON.stringify(json))
let room = JSON.parse(localStorage.getItem('game_room'));
console.log('Response ', room.data[0].title)
});
} else {
console.log('Caching ', room.data[0].title)
}
}
}
}
now this response data save to localStorage but i dont know how to use data to v-for like the my code previously
I am making an application which the user can create projects and assign to a user, for that I am consuming an api made in go, the form sends the data to an api to persist the information and then the api returns the input data to me record the data in the array.
My form in Vue 3:
export default {
setup() {
const name = ref('');
const description = ref('');
const endProject = ref('');
const user = ref(0);
const users = ref();
const listProjects = inject('listProjects');
const router = useRouter();
fetch(`http://localhost:8000/api/v1/users/`)
.then((data) => data.json())
.catch((error) => console.error(error))
.then((response) => {
users.value = response;
});
const saveProject = async () => {
if (
name.value === '' ||
description.value === '' ||
endProject.value === '' ||
user.value === ''
) {
console.log('error');
return;
}
await fetch(`http://localhost:8000/api/v1/projects`, {
method: 'POST',
body: JSON.stringify({
name: name.value,
description: description.value,
end_date: new Date(endProject.value.replace('-', '/')),
user: {
id_user: user.value,
},
}),
headers: {
'Content-Type': 'application/json',
},
})
.then((res) => res.json())
.catch((error) => console.error('error:', error))
.then((data) => {
listProjects.value.push(data);
});
router.push({
name: 'Project',
});
};
return {
name,
description,
endProject,
user,
users,
saveProject,
};
},
};
</script>
The problem happens is when I want to update the api which brings all the projects, I consider that the error occurs because the api where it registers and brings all the information are in different views, one in a form and the other in the view principal.
This is my App.vue
<script>
import { ref } from '#vue/reactivity';
import Navbar from './components/layout/Navbar.vue';
import Sidebar from './components/layout/Sidebar.vue';
import { provide, watchEffect } from '#vue/runtime-core';
export default {
components: { Navbar, Sidebar },
setup() {
const listProjects = ref();
watchEffect(() => {
console.log('bdfbdfb');
fetch(`http://localhost:8000/api/v1/projects`)
.then((res) => res.json())
.catch((err) => console.log(err))
.then((response) => {
listProjects.value = response;
});
});
provide('listProjects', listProjects);
return {
listProjects,
};
},
};
</script>
From comments:
I am expecting that every time the view where the array is pushed is done, App.vue executes the watchEffect
Well it is not how watchEffect works. watchEffect is designed to execute some "side effects" (like fetch call) that depend on some reactive data (for example URL for fetch is created using some ID ref). Yours watchEffect does not depend on any reactive data. It only assign new value to a listProjects ref. (in other words, result of the function you pass to watchEffect does not depend on listProjects ref value at all)
You can debug watchEffect (and watch) using onTrack option. See example bellow. 1st watchEffect does not trigger onTrack because it does not read any reactive data. 2nd does.
const app = Vue.createApp({
setup() {
const projects = Vue.ref([])
Vue.watchEffect(() => {
projects.value = []
}, {
onTrack(e) {
console.log('watchEffect 1 is tracking projects')
}
})
Vue.watchEffect(() => {
console.log(projects.value)
}, {
onTrack(e) {
console.log('watchEffect 2 is tracking projects')
}
})
}
}).mount('#app')
<script src="https://unpkg.com/vue#3.1.5/dist/vue.global.js"></script>
<div id='app'>
</div>
I have been struggling with this issue for a day now. I want to make a copy of the store for user into userCopy so that it can be edited by the user without causing a mutation. My problem is that even though I am using the mounted hook, userCopy only returns an empty store state.
pages/settings/_id.vue
<template>
<div>
{{ user }} // will display the whole object
{{ userCopy }} // will only display empty store object
</div>
</template>
<script>
import { mapState } from 'vuex'
import _ from 'lodash'
data() {
return {
userCopy: {}
}
},
computed: {
...mapState({ user: (state) => state.staff.user })
},
created() {
this.$store.dispatch('staff/fetchUser', this.$route.params.id)
},
mounted() {
this.$data.userCopy = _.cloneDeep(this.$store.state.staff.user)
},
</script>
store/staff.js
import StaffService from '~/services/StaffService.js'
export const state = () => ({
user: {
offers: '',
legal: ''
}
})
export const mutations = {
SET_USER(state, user) {
state.user = user
},
}
export const actions = {
fetchUser({ commit, getters }, id) {
const user = getters.getUserById(id)
if (user) {
commit('SET_USER', user)
} else {
StaffService.getUser(id) // StaffService users axios get call
.then((response) => {
commit('SET_USER', response.data)
})
.catch((error) => {
console.log('There was an error:', error.response)
})
}
},
}
export const getters = {
getUserById: (state) => (id) => {
return state.staff.find((user) => user.id === id)
}
}
Even using this mounted method did not solve the issue. The userCopy object still returns empty.
mounted() {
this.$store
.dispatch('staff/fetchUser', this.$route.params.id)
.then((response) => {
this.userCopy = this.$store.state.staff.user
})
},
It seems that the mounted() is called before your network request get solved.
To fix this, I suggest to do like this.
First:
if (user) {
console.log('user found',user)
commit('SET_USER', user)
return user
} else {
console.log('user not found')
//RETURN the Axios Call here
return StaffService.getUser(id) // StaffService users axios get call
.then((response) => {
commit('SET_USER', response.data)
//return the response here, after committing
return response.data
})
then in your component
mounted() {
this.$store
.dispatch('staff/fetchUser', this.$route.params.id)
.then((response) => {
console.log(response)
this.userCopy = response
})
}
I am trying to fetch replies of a comment in an object.
From the backend,i.e,LARAVEL I receive 1 object. But, in Vue it becomes undefined
Method
fetchReplies(commentid) {
axios
.get("/reply/" + commentid)
.then(res => {
if (res.data != null) {
console.log(res.data);
return res.data;
}
})
.catch(function(err) {
console.log(err);
});
}
OutPut
(2) [{…}, {…}] // for commentid 42
But When used this method in some other method
fetchComments() {
var boardid = this.boardid;
axios
.get("/comment/" + boardid)
.then(res => {
if (res.data != null) {
this.comments = res.data;
console.log(this.fetchReplies(42));
}
})
.catch(function(err) {
console.log(err);
});
},
OutPut
Undefined
Before a while, when i fetch in Vue, I receive 1 object containing data and one with no data. But, suddenly that object with no data disappears.
Your console.log(this.fetchReplies(42)); is calling a function which is still running as axios in asynchronous
If you make your fetchComments an async function, you can wait until your fetchReplies is finished before logging something.
Added a code snippet, make sure axios is returning something as well.
let results = await this.fetchReplies(42)
console.log(results)
const URL = 'https://jsonplaceholder.typicode.com/posts';
new Vue({
el: "#app",
data: {
comments : '',
replies : ''
},
methods: {
fetchReplies(id) {
return new Promise((resolve, reject) => {
axios
.get(URL)
.then(res => {
if (res.data != null) {
resolve(res.data)
} else {
reject('RejectReason')
}
})
.catch(function (err) {
console.log(err);
reject(err)
});
})
},
async fetchComments() {
axios
.get(URL)
.then(async res => {
if (res.data != null) {
//Result from comments is in
this.comments = res.data;
let replies = await this.fetchReplies(1);
this.replies = replies;
}
})
.catch(function (err) {
console.log(err);
});
}
}
})
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="fetchComments()">
Some text
</button>
<h1>Comments</h1>
{{comments}}
<h2>Replies</h2>
{{replies}}
</div>
Update: Change snippet to change data visibly in template
Axios is an asynchronous call, so it seems console.log is called before the fetch call has returned. The most convenient way to use axios call it with es2017 async/await.
How to return a list of all users so they can be listed in a table?
Axios is working fine and a version of this works fine for bringing back a single user. I think it's returning an empty array for some reason.
allUsers ({commit, state}) {
if (!state.idToken) {
return
}
globalAxios.get('/users.json')
.then(res => {
const data = res.data
const allUsers = []
for (let key in data) {
const alluser = data[key]
user.id = key
users.push(user)
}
commit('storeUser', AllUsers)
})
.catch(error => console.log(error))
}
Script:
import axios from 'axios';
export default {
computed: {
allUsers () {
return !this.$store.getters.alluser ? false : this.$store.getters.alluser
},
},
created () {
this.$store.dispatch('allUsers')
}
}
HTML Template:
<p v-if="allUsers">allUsers: {{ allUsers }}</p>
You are not using the variables you defined in your action. It's only a syntax problem. Fix like this:
allUsers ({commit, state}) {
if (!state.idToken) {
return
}
globalAxios.get('/users.json')
.then(res => {
const data = res.data
const allUsers = []
for (let key in data) {
const user = data[key]
user.id = key
allUsers.push(user)
}
commit('storeUser', allUsers)
})
.catch(error => console.log(error))
}
could it be the typo?
allUsers ({commit, state}) {
if (!state.idToken) {
return
}
globalAxios.get('/users.json')
.then(res => {
const data = res.data
const allUsers = []
for (let key in data) {
const alluser = data[key]
user.id = key
users.push(user)
}
commit('storeUser', allUsers) // <--- lower case
})
.catch(error => console.log(error))
}
to display the users...
<div v-if="allUsers">
<div v-for="u in allUsers" :key="u.id">{{ u.email }}</div>
</div>
<div v-else>
...Loading...
</div>