I'm trying to get data from a API that i created, this data is listed just as i wanted, but i can't manipulate this data because it returns a [ob: Observer]. How can i extract data from this ?
Store.js:
items: state => {
item.getAll().then(response => {
state.items = response.data
})
return state.items
},
Component.vue:
...mapGetters(["items"]),
<v-card flat v-for="item in items" :key="item.title">
Just as #skirtle commented i needed to move my function to actions because i'm using a async task.
My final code ended up that way:
getters: {
items: state => state.items,
...
}
mutations: {
'LOAD_ITEMS'(state, data) {
const items = []
for (let key in data) {
const item = data[key];
item.id = key;
items.push(item);
}
state.items = items;
...
}
actions: {
loadItems({ commit, state }) {
...
return axios.get("item.json")
.then(response => {
commit("LOAD_ITEMS", response.data);
}).catch(error => console.log(error));
}
}
I'm using firebase, so i did a loop to store the key.
If you has your own API maybe you can just store response.data directly in a JS object without using a loop.
Related
I have this function return a call back as:
function fetchShifts(ctx, callback) {
const accountId = selectedAccount.value.value.id
store.dispatch('app-action-center/fetchShifts', {
accountId,
})
.then(shifts => {
const data = []
shifts.forEach(async (shift, index) => {
const user = await store.dispatch('app-action-center/fetchUserDetails',
{
assignedTo: shift.assignedTo,
})
.then(res => res)
data.push({
...shift,
user: user.fullName,
})
if (index === (shifts.length - 1)) { callback(data) }
})
})
}
In the vue file I try to set it as:
data() {
return {
shifts: this.fetchShifts,
}
},
or
data() {
return {
shifts: null,
}
},
created() {
this.shifts = this.fetchShifts()
}
None of them work, I want to make this shifts variable ready when the component mounted so I can put it in the <app v-for="shift in shifts" />
At this moment, this code work fine with <b-table :items="fetchShifts /> but I don't know how to convert to <ul v-for="shift in shifts></ul>
Try like this:
<ul v-for="shift in shifts" :key="shift.id">
</ul>
export default
{
data()
{
return {
shifts: [],
};
},
created()
{
this.fetchShifts(undefined, (shiftsArray) =>
{
this.shifts = shiftsArray;
});
}
}
Explanation - initially you start with an empty array. Then you asynchronously fetch the shifts. The callback is called as soon as all the shifts and the corresponding users have been fetched - and in this callback you update the array with the shifts, which in turn triggers component re-rendering.
Vue is truly amazing!
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;
}
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'm trying to experiment on displaying data using VueX and a free API from rapidapi. Somehow I can't display or iterate through it properly in the component.
The console displays the objects correctly, but the component that's supposed to display it does not.
What am I doing wrong?
Here are the relevant codes:
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
worldData:
fetch("https://covid-193.p.rapidapi.com/statistics", {
method: "GET",
headers: {
"x-rapidapi-host": "covid-193.p.rapidapi.com",
"x-rapidapi-key": "mySecretKey"
}
})
.then(response => response.json())
.then(data => {
data.response.sort((a, b) => (a.country > b.country ? 1 : -1));
console.log(data.response);
return data.response;
})
},
getters: {
worldData: state => state.worldData,
},
mutations: {
},
actions: {
},
modules: {
}
})
components/mycomponent.vue
<template>
<div >
<div v-for="myData in $store.getters.worldData" :key="myData">{{myData}}</div>
</div>
</template>
When you create a store, the state property is for initial / default values. You are currently setting yours to a Promise which is probably not what you want.
Performing asynchronous tasks should be done via actions and the results committed through mutations.
const store = new Vuex.Store({
state: {
worldData: [] // initial value
},
getters: {
worldData: state => state.worldData
},
mutations: {
setWorldData: (state, worldData) => state.worldData = worldData
},
actions: {
loadWorldData: async ({ commit }) => {
// load the data via fetch
const res = await fetch('https://covid-193.p.rapidapi.com/statistics', {
headers: {
"x-rapidapi-host": "covid-193.p.rapidapi.com",
"x-rapidapi-key": "mySecretKey"
}
})
// check for a successful response
if (!res.ok) {
throw res
}
// parse the JSON response
const worldData = (await res.json()).response
// commit the new value via the "setWorldData" mutation
commit('setWorldData', worldData.sort((a, b) => a.country.localeCompare(b.country)))
}
}
})
store.dispatch('loadWorldData') // dispatch the action to load async data
export default store
You can execute the dispatch anywhere at any time to load / reload the data.
I'm using vueJs MDB-datatable to display my data coming from my API.
I followed the MDB-datable documentation in handling the "OtherJSON structure" but it didn't re-render the data from the API request.
I tried different callback beforeCreate, created, beforeMount, and mounted, the data was changed but still, it didn't render the latest data.
Here's the code:
<template>
<mdb-datatable
:data="tableData"
striped
bordered
/>
</template>
<script>
import 'mdbvue/build/css/mdb.css';
import { mdbDatatable } from 'mdbvue';
export default {
components: {
mdbDatatable
},
data: () => ({
tableData: {
columns: [],
rows: []
}
}),
created() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(json => {
let keys = ["id", "name", "username"];
let entries = this.filterData(json, keys);
//columns
this.tableData.columns = keys.map(key => {
return {
label: key.toUpperCase(),
field: key,
sort: 'asc'
};
});
console.log(this.tableData.columns);
//rows
entries.map(entry => this.tableData.rows.push(entry));
console.log(this.tableData.rows);
})
.catch(err => console.log(err))
},
methods: {
filterData(dataArr, keys) {
let data = dataArr.map(entry => {
let filteredEntry = {};
keys.forEach(key => {
if(key in entry) {
filteredEntry[key] = entry[key];
}
})
return filteredEntry;
})
return data;
}
}
</script>
The MDB-datatable documentation seems to be straight forward but I don't know which part I'm missing.
I'm new to VueJS. Any help is much appreciated.
It seems that the current version of MDB Vue (5.5.0) takes a reference to the rows and columns arrays and reacts when these arrays mutate rather than reacting to changes to the property bound to the data prop itself.
I see you are already mutating rather than replacing the rows array, so you need to do the same with the columns array.
//columns
this.tableData.columns.push(...keys.map(key => {
return {
label: key.toUpperCase(),
field: key,
sort: 'asc'
};
}));