Vue2 Composition Api - How do I fetch data from api? - vuejs2

I am using Vue2.6 with composition api.
I need to reroute to different pages depends on an api response.
Can someone please guide me, please?
I tried using onBeforeMount but it renders the UI elements then rerouted to the corresponding page to the api response..so I can see a flash of the wrong UI..
setup() {
const myData = 'myData';
onBeforeMount(async () => {
try {
const results = await fetchData();
// do reroute depends on results response
}
} catch (err) {
console.log(err);
}
});
return {
myData,
};
I also tried adding async in the setup method but it errored saying my ref variables "Property or method "myData" is not defined on the instance but referenced during render."
async setup() {
const myData = 'myData';
onMounted(async () => {
try {
const results = await fetchData();
// do reroute depends on results response
}
} catch (err) {
console.log(err);
}
});
return {
myData,
};

It looks like you're trying to handle routing (re-routing) dynamically from inside a component. I can't see the rest of the apps, so can't speak to the validity of such a solution, but would like you dissuade you from doing that. routing logic should, IMO, not be handled in a component. The components should mostly just handle the template and user interaction. By the time you're rendering a component, that API should have been resolved already.
I would recommend to resolve the API response before the route is
even completed. You or use a navigationGuard to resolve the API during the route execution. This functionality is asynchronous, so you can await the response before proceeding.
Alternatively, if you really want to handle it in the component, you will have that delay while the API is resolving, but you can implement some loader animation to improve the experience.

Related

Vue - Best way to poll the same API endpoint for multiple components simultaneously

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?

Async Vue lifecycles

I have the following situation in my Vue.js application:
data() {
return {
data: []
}
},
async created() {
console.log('before async call')
try {
// async call ...
console.log('after async call')
this.data = // data from api call
} catch (err) {
// handle err
}
},
mounted() {
console.log('mounted')
init(this.data)
}
When I run the code I get:
before async call
mounted
after async call
As a result of this the init method which is a class constructor in mounted gets called with an empty array instead of the data from the API call. What I would like is execute things synchronously and not execute mounted until the data is available. I understand that the above problem is how Vue executes lifecycles when there is async code included, but how can you solve problems like these?
async lifeCycles in Vue are a misleading syntax.
Every Vue lifecycle is only a trigger to run whatever code you put in there at that particular time.
But Vue doesn't wait for the promise to resolve and hold everything else (related to the component's lifecycle) until it happens. In effect, all you do is delay the execution of the code you place in the lifecycle until some promise resolves.
To give you a better understanding of what's going on, the following syntaxes are equivalent:
async created() {
const data = await fetchSomeData();
// rest of code depending on `data`
}
The equivalent:
created() {
fetchSomeData().then(data => {
// rest of code depending on `data`
});
}
Because async lifecycles are a misleading syntax, it's typically discouraged in applications developed by large teams, in favor of the .then() syntax. This is to avoid minor bugs created by misunderstanding when code actually runs. For example, if a new developer places some code into an async hook (without looking closely at the rest of the code in the hook), the code might run later than intended, unless placed before any await.
To fix whatever errors you're trying to fix, just wrap what can't be rendered until actual data has resolved into a if (inside component) or v-if (inside template).
Typical usage examples:
computed: {
someComputed() {
if (this.data.length) {
// returned when data has length
return something
}
// returned when data has no length
return somethingElse
}
}
or:
<div v-if="data.length">
<!-- markup depending on actual data... -->
</div>
<div v-else>
loading...
</div>
Note: the computed above will automatically react to the change in data's length (no need for a watch), because computed properties get re-run whenever their internal reactive references change values. As you'd expect, they recalculate/rerender anything depending on them. Same is true for <v-if>. Internally they both use what's known as Vue's "injections and reactivity".
If I were you, I would put all the logic within either mounted or created hook. If for some reason you need to wait until mount for your code to work, fetch the data and init it within mounted:
async mounted() {
try {
// async call ...
console.log('after async call')
this.data = await // data from api call
init(this.data)
} catch (err) {
// handle err
}
}
You've almost nothing to gain in terms of performance to split the code between created and mounted.
Still, if for some reason you really need to place them in different hooks, you could store data in a promise:
async created() {
console.log('before async call')
try {
// async call ...
console.log('after async call')
this.data = fetch('whatever') // now this.data is a promise
} catch (err) {
// handle err
}
},
async mounted() {
console.log('mounted')
init(await this.data)
}
This should work, but I don't think it would be worth the hassle.
As a clarification for whoever may read this: you can use async/await in vue lifecycle hooks in order to code within to await for promises etc. However, this won't make Vue to actually await for the lifecycles itselves: so you can end withmounted code running before async code from created. The fact that Vue doesn't wait for async lifecycles doesn't mean is bad to place async/await code in them.

Can I cache an API call object using Vue JS lifecycle hooks?

I'm using The Strain API which has a search query that gets all the strains in its database. It says to use this sparingly as it requires a lot of computing power. My question is: can I call it once in the App component life cycle hook, save the response in the App data(), and cache it somehow so that if the data.object.length != 0, it doesn't try calling it again? Then I could pass it as a prop to any other component that needs it. Sorry, new to VueJs and programming.
the best thing in my opinion is to keep the calculated data in a service, so that you in your mounted component simply call the getData (), imported from the StrainService.js
// StrainService.js
let response = null;
const getData = async () => {
if (!response) {
response = await StrainApiCall()
}
return response
}
const StrainApiCall = () => {
return axios.get('yourdata.com') // your api call
}
export {
getData
}

How to handle ajax errors reusably with Vuex?

I am building an SPA and I have a couple of different forms that submit data to an API. I am using axios for the ajax calls and have built a wrapper class around it for my use-case, called api. Inside that class I handle errors thrown by that instance.
The problem is I was storing an instance of the api class in each form's state. I later realized that functions shouldn't live in the state due to serialization.
The reasoning behind having the api class in the state was so that all of the children components of a form could access the errors and display their respective error along with removing the error on update.
One solution could be using an axios interceptor and commit all errors to a global errors module. But then I wouldn't know which errors belong to which form, in case two forms (or other requests) were submitted at the same time. I could of course save the errors in regard to the request URI, but then I would also have to take the request method into consideration.
reportError(state, { uri, method, errors }) {
state.errors[uri + '#' + method] = errors;
}
Then I could have a getter like:
getErrorsByRequest: state => ({ uri, method }) => {
return ...
}
But this feels unnecessarily awkward.
Since what I am trying to achieve is most likely very common, I am wondering, how do I sanely handle ajax errors reusably with Vuex?
I was checking for my old projects, and i did something similar:
This is my axios instance interceptor:
axiosInstance.interceptors.response.use(response => response, error => {
const { status } = error.response
...
...
// data invalid (Unprocessable Entity)
if (status === 422) {
// errors list from response
const dataErrors = error.response.data.errors
let objErrors = {}
// joining just the first error from array errors as value for each prop
for (let key in dataErrors) {
objErrors[key] = dataErrors[key].join()
}
// commiting to errors module
store.commit('errors/SET_ERRORS', objErrors)
}
...
...
})
Here my store module errors:
export const state = {
form_errors: {}
}
export const mutations = {
SET_ERRORS: (state, errors) => { state.form_errors = errors },
CLEAN_ERRORS: (state) => { state.form_errors = {} }
}
Using on components:
computed: {
...mapState('errors', ['form_errors'])
}
Using on form template:
<q-input
v-model="form.description"
:error-message="form_errors.description"
:error="!!form_errors.description"
/>

using async data in my page using nuxtjs

I have read using async data or fetch is a better approach in pages in nuxtjs instead of using the created hook.
I am struggling to get my code to work though
I had the following (Which does work fine)
created () {
this.$store.dispatch('cases/getCase', this.$route.params.caseId );
},
But how would I change that to work with the async method instead please, and be able to return more than one state when I need to.
I tried the following
async asyncData ({ params }) {
const thisCase = await this.$store.dispatch('cases/getCase', this.$route.params.caseId );
// constant thisUser
return { thisCase }
// return { thisCase, thisUser}
},
but this generated an error
undefined is not an object (evaluating 'this.$store')
Can anyone tell me what I am doing wrong please
Thanks
this not available in asyncData/fetch. It is even stated in docs in special orange warning.
You do NOT have access of the component instance through this inside
asyncData because it is called before initiating the component.
And again as said in docs
method receives the context object as the first argument, you can use
it to fetch some data and return the component data.
Context is where from you should be getting your store. Here docs for context.
So your code would be
async asyncData ({ params, store }) {
const thisCase = await store.dispatch('cases/getCase', params.caseId )
return { thisCase }
},