I have a relatively small app that I would like to call multiple api's as the app gets loaded initially and store those results (without Vuex) so that it could be made available via provide/inject to child components.
In my App.vue currently, I have this:
export default {
setup() {
const data = ref([]);
const issues = ref([]);
(async () => {
const res = await fetch("/api/lists").then({ data });
data.value = await res.json();
data.value =
data.value &&
data.value.data.map((c) => {
return { ...c.attributes, id: c.id };
});
})();
(async () => {
const res = await fetch("/api/issues").then({ issues });
issues.value = await res.json();
issues.value =
issues.value &&
issues.value.data.map((c) => {
return [{ ...c.attributes, id: c.id }];
});
})();
provide("items", data);
provide("issues", issues);
return {};
},
components: {},
The items data get's rendered 100% of the time in the child component. The issues data get's rendered maybe 10% of the time and I'm assuming it's because the async function doesn't finish before the provide statements run.
My questions are
Is there a better way to call multiple fetch statements at once and assign those results to a variable I can "provide?"
What's the best way to preload these api calls (there's about 3 or 4 more I need to implement) when a user first visits the site and have the results made available without having to re-fetch every time a user switches between views and without using Vuex.
You can do it like this: https://stackblitz.com/edit/vue-y5hetf
Promise.all is for running the fetch calls in parallel, instead of doing it serially which helps improving performance.
As long as you are doing those requests in the App.vue file, these calls should only happen on reload, not on route changes.
Related
I am making a weather app written in Vue.js, which fetches weather data periodically, but I have an issue rendering new data after the initial API call.
The empty data array is declared in the data, and a timer is used to fetch new data, as such:
data() {
return {
weatherData: [],
timer: '',
};
},
I have declared the data fetching in methods, as such:
methods: {
async fetchWeatherData() {
const response = await fetch("http://localhost:5002/timeseries");
const data = await response.json();
if (response.ok) {
console.log("Data fetched sucssefully!");
}
return data;
},
And when the app loads, the fetchWeatherData and setInterval is initiated:
async created() {
this.weatherData = await this.fetchWeatherData();
setInterval(() => this.timer = this.fetchWeatherData(), 10000)
},
The problem is that the new data is not rendered to the DOM, although new data is fetched successfully.
What would be the best step to ensure that the new data is rendered correctly upon successfull fetch?
-HK
In the component (or any container) where you render the weather data, add a key (like :key="renderWeatherKey"). Add renderWeatherKey to component data.
data() {
return {
weatherData: [],
timer: '',
renderWeatherKey: 0
};
},
In the method fetchWeatherData(), inside 'if' condition, add this.renderWeatherKey++ :
async fetchWeatherData() {
const response = await fetch("http://localhost:5002/timeseries");
const data = await response.json();
if (response.ok) {
console.log("Data fetched sucssefully!");
this.renderWeatherKey++
}
return data;
},
You can force the re rendered with that.
The solution, as posted by James Thomson over at the Vue forum, was to set the setInterval to async, since the fetch method also is async. See the full answer here.
Im using nuxt and vuex. In vuex im getting data:
actions: {
get_posts(ctx) {
axios.get("http://vengdef.com/wp-json/wp/v2/posts").then(post => {
let posts = post.data;
if (!posts.length) return;
let medias_list = "";
posts.forEach(md => {
medias_list += md.featured_media + ","
});
medias_list = medias_list.slice(0, -1);
let author_list = "";
posts.forEach(md => {
author_list += md.author + ","
});
author_list = author_list.slice(0, -1);
axios.all([
axios.get("http://vengdef.com/wp-json/wp/v2/media?include=" + medias_list),
axios.get("http://vengdef.com/wp-json/wp/v2/users?include=" + author_list),
axios.get("http://vengdef.com/wp-json/wp/v2/categories"),
]).then(axios.spread((medias, authors, categories) => {
ctx.commit("set_postlist", {medias, authors, categories} );
})).catch((err) => {
console.log(err)
});
})
}
},
In vuex state i have dynamic postlist from exaple below.
How i can use it in Nuxt?
In nuxt i know async fetch and asyncData.
async fetch () {
this.$store.dispatch("posts/get_posts");
}
Thats not working.
How i can say to nuxt, wait loading page, before vuex actions loading all data?
As you already mentioned there are:
fetch hook
asyncData
And differences are well described here
The reason why your code is not working might be in your store action.
It should return a promise, try to add return before axios get method ->
get_posts(ctx) {
return axios.get(...
// ...
And then, on your page:
async fetch () {
await this.$store.dispatch("posts/get_posts");
}
Also, in comment above you're saying that you dont want to commit data in store:
...load page only after vuex, i dont need to pass data in vuex
But you do it with this line:
ctx.commit("set_postlist", {medias, authors, categories} );
if you dont want to keep data in store, just replace line above with:
return Promise.resolve({ medias, authors, categories })
and get it on your page:
async fetch () {
this.posts = await this.$store.dispatch("posts/get_posts");
// now you can use posts in template
}
Misread the actual question, hence the update
With Nuxt, you can either use asyncData(), the syntax will change a bit tho and the render will be totally blocked until all the calls are done.
Or use a combo of fetch() and some skeletons to make a smooth transition (aka not blocking the render), or a loader with the $fetchState.pending helper.
More info can be found here: https://nuxtjs.org/docs/2.x/features/data-fetching#the-fetch-hook
Older (irrelevant) answer
If you want to pass a param to your Vuex action, you can call it like this
async fetch () {
await this.$store.dispatch('posts/get_posts', variableHere)
}
In Vuex, access it like
get_posts(ctx, variableHere) {
That you can then use down below.
PS: try to use async/await everywhere.
PS2: also, you can destructure the context directly with something like this
get_posts({ commit }, variableHere) {
...
commit('set_postlist', {medias, authors, categories})
}
I need to fetch continuously updating API endpoint data for several components on my NUXT site, 2 of which are visible simultaneously at any given moment. I wonder what is the best practice to poll the same endpoint for several different components visible at the same time?
Currently, I am using VUEX to send the API data to my components and then using setInterval with a refresh function to update the data in each component. This is clearly a clumsy solution (I am a beginner). I was thinking about polling the API directly in VUEX but I understand this is not advisable either.
This is my current (clumsy) solution:
VUEX:
// STATE - Initial values
export const state = () => ({
content: {}
});
// ACTIONS
export const actions = {
async nuxtServerInit ({ commit }) {
const response = await this.$axios.$get('https://public.radio.net/stations/see15310/status');
commit('setContent', response)
}
}
// MUTATIONS
export const mutations = {
setContent(state, content) {
state.content = content;
}
}
And in each component:
computed: {
content () {
return this.$store.state.content
},
methods: {
refresh() {
this.$nuxt.refresh()
}
},
mounted() {
window.setInterval(() => {
this.refresh();
}, 5000);
I think, it's a normal solution to do the polling in vuex. Vuex is your application state and the one source of truth for all dependant components. And if you need to update some similar state for different components - it's a rational solution to do it in vuex action.
Another solution could be the event bus. First article about it from google
Also, I don't recommend use SetInterval for polling. Because it's don't wait of async operation ending. This fact could shoot you in the foot, if a client has network delay or another glitch. I used SetTimeout for this purpose.
async function getActualData() {
// get data from REST API
}
async function doPolling() {
const newData = await getActualData() // waiting of finish of async function
// update vuex state or send update event to event bus
setTimeout(doPolling, 5000)
}
doPolling()
If my answer missed into your question, then give me more details please. What is the problem you want to solve? And what disadvantages do you see in your(by your words ;) ) "clumsy" solution?
apologies for the simple question, I'm really new to Vue/Nuxt/Vuex.
I am currently having a vuex store, I wish to be able to populate the list with an API call at the beginning (so that I would be able to access it on all pages of my app directly from the store vs instantiating it within a component).
store.js
export const state = () => ({
list: [],
})
export const mutations = {
set(state, testArray) {
state.list = testArray
}
}
export const getters = {
getArray: state => {
return state.list
},
}
I essentially want to pre-populate state.list so that my components can call the data directly from vuex store. This would look something like that
db.collection("test").doc("test").get().then(doc=> {
let data = doc.data();
let array = data.array; // get array from API call
setListAsArray(); // put the array result into the list
});
I am looking for where to put this code (I assume inside store.js) and how to go about chaining this with the export. Thanks a lot in advance and sorry if it's a simple question.
(Edit) Context:
So why I am looking for this solution was because I used to commit the data (from the API call) to the store inside one of my Vue components - index.vue from my main page. This means that my data was initialized on this component, and if i go straight to another route, my data will not be available there.
This means: http://localhost:3000/ will have the data, if I routed to http://localhost:3000/test it will also have the data, BUT if i directly went straight to http://localhost:3000/test from a new window it will NOT have the data.
EDIT2:
Tried the suggestion with nuxtServerInit
Updated store.js
export const state = () => ({
list: [],
})
export const mutations = {
set(state, dealArray) {
state.list = dealArray
}
}
export const getters = {
allDeals: state => {
return state.list
},
}
export const actions = {
async nuxtServerInit({ commit }, { req }) {
// fetch your backend
const db = require("~/plugins/firebase.js").db;
let doc = await db.collection("test").doc("test").get();
let data = doc.data();
console.log("deals_array: ", data.deals_array); // nothing logged
commit('set', data.deals_array); // doesn't work
commit('deals/set', data.deals_array); // doesn't work
}
}
Tried actions with nuxtServerInit, but when logging store in another component it is an empty array. I tried to log the store in another component (while trying to access it), I got the following:
store.state: {
deals: {
list: []
}
}
I would suggest to either:
calling the fetch method in the default.vue layout or any page
use the nuxtServerInit action inside the store directly
fetch method
You can use the fetch method either in the default.vue layout where it is called every time for each page that is using the layout. Or define the fetch method on separate pages if you want to load specific data for individual pages.
<script>
export default {
data () {
return {}
},
async fetch ({store}) {
// fetch your backend
var list = await $axios.get("http://localhost:8000/list");
store.commit("set", list);
},
}
</script>
You can read more regarding the fetch method in the nuxtjs docs here
use the nuxtServerInit action inside the store directly
In your store.js add a new action:
import axios from 'axios';
actions: {
nuxtServerInit ({ commit }, { req }) {
// fetch your backend
var list = await axios.get("http://localhost:8000/list");
commit('set', list);
}
}
}
You can read more regarding the fetch method in the nuxtjs docs here
Hope this helps :)
Within a records module, there is an action, getRecords to retrieve all records from the API at /records, which commits the setRecords mutation which sets the state for records. A getter for records also exists.
Within the records vue, the created method calls the getRecords action, and the getter for records is passed to the datatable for display.
Until now everything works, however when navigating on and off the records vue, the API is called each time.
How is the properly handled? Does a best practice exist? I can move the action call to a higher level, but this generates an API that may not be required id the user never visits the records vue.
records module
const getters = {
allRecords: state => state.records
}
const state = {
records: []
}
const actions = {
async getRecords({commit}){
console.log('getting records...');
const response = await axios.get('/api/records')
commit('setRecords', response.data)
},
async addRecord({ commit }, user) {
console.log("user :" + JSON.stringify(user))
const response = await axios.post('/api/records', user)
.catch(err => console.log(err))
commit('addRecord', response.data)
}
}
const mutations = {
setRecords: (state, records) => (state.records = records),
addRecord: (state, user) => ([...state.records, user])
}
export default {
state,
getters,
actions,
mutations
}
I have handled this in various different ways in the past.
If you do not care if the data you are serving might be old, you can simply detect if you already have some items in your array:
const actions = {
async getRecords({ commit, getters }){
if (getters.allRecords.length > 0) {
// Don't bother retrieving them anymore
return;
}
console.log('getting records...');
const response = await axios.get('/api/records')
commit('setRecords', response.data)
},
async addRecord({ commit }, user) {
console.log("user :" + JSON.stringify(user))
const response = await axios.post('/api/records', user)
.catch(err => console.log(err))
commit('addRecord', response.data)
}
}
If you do want to have the updated values in your database, you can consider changing your api to only return changed records after a certain timestamp. For this we need the following:
We need to store the timestamp of our last update. The first time we would retrieve everything, and on subsequent requests we would send the timestamp.
A way to identify which records to update in the local state, and which records to delete or add. Having something like an id on your records might be helpful.
Let's assume that instead of returning a flat array of your records, the api returns a response in the format
{
records: [ ... ],
removedRecords: [ ... ],
timestamp: 123456789
}
You would change your state to
const state = {
records: [],
recordUpdateTimestamp: null
}
Your action would then look something like this:
async getRecords({ commit, state }){
const config = {};
if (state.recordUpdateTimestamp) {
config.params = {
timestamp: state.recordUpdateTimestamp
};
}
console.log('getting records...');
const { data }= await axios.get('/api/records', config)
commit('setRecords', data.records);
commit('removeRecords', data.removedRecords);
commit('setRecordUpdateTimestamp', data.timestamp);
},
I will leave writing the mutations to you.
This would obviously need work in the backend to determine which records to send back, but may have the advantage of cutting down both the amount of returned data and the time processing that data a lot.
FYI you don't need a shallow getter like the one you have.
Meaning that a getter that doesn't compute your state has no value might as well use the state itself.
About the practice, it really depends on how important it is to you that "records" has always the freshest data. If you don't need it to be always fetched, you can have a "initRecords" action to run on your "App.vue" on created hook that you can use to initialize your records. If you need always fresh data, what you have is good enough.