In App.Vue i have this code:
<script>
import axios from "axios";
import { baseApiUrl, userInfo } from "#/global";
export default {
methods: {
moveToLoginPage() {
localStorage.removeItem(userInfo);
this.$store.commit("setToken", null);
this.$router.push({ path: "/login" });
},
async validateToken() {
const json = localStorage.getItem(userInfo);
const userData = JSON.parse(json);
if (!userData) {
this.moveToLoginPage();
} else {
const url = `${baseApiUrl}/auth/validateToken`;
const resData = await axios.post(url, userData).then(resp => resp.data);
if (!resData) {
this.moveToLoginPage();
}
}
}
},
created() {
this.validateToken();
}
};
</script>
After access the system I go to any other page, and give a refresh. When the page is reloaded, this code snippet from Vue.app is executed to validate the token from localStorage. But before I get the response from the server in validateToken() function, other HTTP.GET requests are executed in the page I has refreshed in function mounted(). I would like the validateToken() function to fully execute before the other page's mounted() function is executed.
You can use async/await like so:
async created(){
await this.validateToken();
}
This will prevent the create lifecycle hook from completing until the validateToken returns a response
I am assuming you have a router-view in your App.vue file.
You can keep a flag / variable for your async data and then load the router-view only if that data is available.
<template>
<router-view v-if="data"></router-view>
</template>
This way, your inner children aren't even loaded till the time your async data is available.
This answer is just the same comment I made on the question above but for future people to come.
Related
so guys I've tried to fetch data and show it inside my component. But the problem is, fetching data got 404 or data not show for the first load.. but when I try to reload again data is shown as should be. And one thing.. the data was success upload to the server even the response status 404 as I said
By the way guys this is my component.js
getApi() {
return api.get("/routeName")
}
this is my Store
async fetchApi({ commit }) {
try {
let {
data: { data }
} = await component.getApi()
commit("SET_API", data)
} catch (error) {
return Promise.reject(error)
}
}
and this is how I call fetchApi from a store inside my component
async created() {
await this.getApi()
}
methods: {
async getDraft() {
try {
await this.$store.dispatch("component/fetchApi")
this.scrollToTop()
} catch (error) {
error
}
}
}
I assume you access VUEX state data in <template> tag (I cannot comment to get more information.)
If I right, the problem is compoent not watch VUEX state when data null while you access.
If you render when VUEX state data is null, when data update while you call fetchDraft in yours store. In component not track your VUEX. I don't know why it happen in low level.
In my case, I need to use setInterval to load Vuex state to local data in component. And use it instead of access direct to vuex.
ex.
created() {
this.intervalUpdate = setInterval(() => {
this.updateData();
}, 1000);
},
destroyed() {
clearInterval(this.intervalUpdate);
},
methods: {
async updateData() {
this.draftData = await this.$store.dispatch("review/fetchDraft");
}
}
It's not the best solution. But its help me to solve problem that component
I'm building an app with Vue and Nuxt w/ Firebase.
I have this Action in the store:
async loadPrev({commit, rootState}, payload) {
try{
let doc = await this.$fireStore.collection(`users/`+ rootState.user.uid + `/preventivi`).doc(payload).get()
commit(`setLoadedPrev`, doc.data())
}
catch(e){
console.log(e)
}
},
In a page component (a dynamic route), I load the information about that precise doc in Firestore via the created() hook:
data(){
return{
prevDetails: {}
}
},
computed: {
...mapGetters({
getLoadedPrev: `prevs/getLoadedPrev`
})
},
methods:{
async loadPrevInfo(){
try{
await this.$store.dispatch(`prevs/loadPrev`, this.$route.params.id)
this.prevDetails = {...this.getLoadedPrev}
}
catch(e){
console.log(e)
}
}
},
created(){
this.loadPrevInfo()
}
Now, everything seems to work, in fact when I go the route that matches the ID of the document in Firestore it loads all the data correctly.
My question is: does the loadPrevInfo() method wait for the dispatch to be completed? I fear that if the data to retrieve are big, the app sets this.prevDetails = {...this.getLoadedPrev} before the dispatch method retrieves all the data from firestore. Do I have to return a promise on the loadPrev action? How?
No, because async/await works the similar way as promise, and code after await will wait until execution of function with await will be finished.
You could also make 'created' async to be able await for loading info.
I have a component that fetches data asynchronously in componentDidMount()
componentDidMount() {
const self = this;
const url = "/some/path";
const data = {}
const config = {
headers: { "Content-Type": "application/json", "Accept": "application/json" }
};
axios.get(url, data, config)
.then(function(response) {
// Do success stuff
self.setState({ .... });
})
.catch(function(error) {
// Do failure stuff
self.setState({ .... });
})
;
}
My test for the component looks like this -
it("renders the component correctly", async () => {
// Have API return some random data;
let data = { some: { random: ["data to be returned"] } };
axios.get.mockResolvedValue({ data: data });
const rendered = render(<MyComponent />);
// Not sure what I should be awaiting here
await ???
// Test that certain elements render
const toggleContainer = rendered.getByTestId("some-test-id");
expect(toggleContainer).not.toBeNull();
});
Since rendering and loading data is async, my expect() statements go ahead and execute before componentDidMount() and the fake async call finish executing, so the expect() statements always fail.
I guess I could introduce some sort of delay, but that feels wrong and of course increases my runtime of my tests.
This similar question and this gist snippet both show how I can test this with Enzyme. Essentially they rely on async/await to call componentDidMount() manually.
However react-testing-library doesn't seem to allow direct access to the component to call its methods directly (probably by design). So I'm not sure "what" to wait on, or whether that's even the right approach.
Thanks!
It depends on what your component is doing. Imagine your component shows a loading message and then a welcome message. You would wait for the welcome message to appear:
const { getByText, findByText } = render(<MyComponent />)
expect(getByText('Loading...')).toBeInTheDocument()
expect(await findByText('Welcome back!')).toBeInTheDocument()
The best way to think about it is to open the browser to look at your component. When do you know that it is loaded? Try to reproduce that in your test.
You need to wrap render with act to solve warning message causes React state updates should be wrapped into act.
e.g:
it("renders the component correctly", async () => {
// Have API return some random data;
let data = { some: { random: ["data to be returned"] } };
axios.get.mockResolvedValue({ data: data });
const rendered = await act(() => render(<MyComponent />));
// Test that certain elements render
const toggleContainer = rendered.getByTestId("some-test-id");
expect(toggleContainer).not.toBeNull();
});
Also same goes for react-testing-library.
Here's my child's component async method:
async created () {
this.$parent.$emit('loader', true)
await this.fetchData()
this.$parent.$emit('loader', false)
}
fetchData does an axios get call, to fetch data from API. However in a vue-devtools (events tab) i can only see the events, after i change the code and it hot reloads. Also i've set up console.log() in a parent component:
mounted() {
this.$on('loader', (value) => {
console.log(value)
})
}
And i can see only false in a console. My purpose is to emit loader set to true (so i can show the loader), then set it to false, when data is fetched.
My fetchData method:
import http from '#/http'
fetchData() {
return http.getOffers().then((resp) => {
this.offersData = resp.data
})
}
Contents of http.js:
import axios from 'axios'
import config from '#/config'
const HTTP = axios.create({
baseURL: config.API_URL
})
export default {
/* calculator */
getOffers() {
return HTTP.get('/url')
}
}
If i directly use return axios.get() in async created(), then it works. Problem is in this imported http instance.
Final solution
One of the problems was using different lifecycles, thanks to Evan for mentioning this.
Another problem was with async / await usage, changes to a fetchData() method:
import http from '#/http'
async fetchData() {
await http.getOffers().then((resp) => {
this.offersData = resp.data
})
}
I had to make this method async and use await on axios request, since await is thenable, it does work. Also i've spotted an issue in https.js:
export default {
/* calculator */
getOffers() {
return HTTP.get('/url')
}
}
It returns HTTP.get(), not a promise itself, i could have used then here, and it would work, but, for flexibility purposes i didn't do that.
But, still, i don't get why it didn't work:
fetchData() {
return http.getOffers().then((resp) => {
this.offersData = resp.data
})
}
Isn't it already returning a promise, since it's chained with then... So confusing.
Retested again, seems like return is working, lol.
The issue here is that created on the child component is getting called before mounted on the parent component, so you're beginning to listen after you've already started your Axios call.
The created lifecycle event method does not do anything with a returned promise, so your method returns right after you begin the Axios call and the rest of the vue component lifecycle continues.
You should be able to change your parent observation to the created event to make this work:
created() {
this.$on('loader', (value) => {
console.log(value)
})
}
If for some reason you need to do something that can't be accessed in created, such as accessing $el, I'd suggest moving both to the mounted lifecycle hook.
I'd simply suggest restructuring your method, as there isn't really a need to make an async method since axios itself is asnychronus.
If you already have the fetchData method defined, and the goal is to toggle the loader state when a call is being made, something like this should do.
fetchData () {
this.$parent.$emit("loader", true)
axios.get(url)
.then(resp => {
this.data = resp
this.$parent.$emit("loader", false)
})
}
Of course these then statements could be combined into one, but it's the same idea.
Edit: (using the parent emit function)
fetchData () {
this.loader = true
axios.get(url)
.then(resp => this.data = resp)
.then(() => this.loader = false)
}
If what you are trying to achieve is to tell the direct parent that it's no longer loading, you would have to emit to the same instance like so
async created () {
this.$emit('loader', true)
await this.fetchData()
this.$emit('loader', false)
}
By removing the$parent, you will emit from the current component.
--Root
--My-page.vue
-Some-child.vue
Now you will emit from some-child.vue to my-page.vue. I have not tried, but theoretically what you are doing by emiting via parent: (this.$parent.$emit('loader', false)) You are emitting from my-page.vue to root.
So If you have a $on or #loader on the component like so: <Some-child #loader="doSomething"/>, This will never run due to you emitting from the parent.
Am trying to fetch data in the fetch method of my page using vuex and nuxt js but whenever a person refreshes the page it fails but works when i navigate through nuxt navigation
So in my page i have
fetch ({ store, params }) {
store.dispatch('getJobspecialisims')
},
//IVE ALSO TRIED ASYNC
async asyncData (context) {
await context.store.dispatch('getJobspecialisims');
},
SO in my vuex action i have
async getJobspecialisims({commit}){
await axios.get(process.env.baseUrl+'/job-specialisims').then((res)=>{
commit(types.SET_JOB_SPECIALISIMS, res.data.data)
},(err)=>{
console.log("an error occured ", err);
});
},
Now on my component where am fetching the records
<div>
<li v-for="(specialisim) in $store.getters.jobspecialisims">
<!-DO STUFF HERE->
</li>
The http request is nver sent when a person refreshes the browser
Where am i going wrong?
I would prefer the async await method. I uderstand that it returns a promise but i have no idea on how to know when the promise has resolved. Thepage should always await untill the data has been completely ffetched hence ive called the fetch or asyncdata method in my page
What else do i need to add or amend?
For async actions, you need to put return before dispatch when you call it inside fetch or asyncData:
fetch ({ store, params }) {
// store.dispatch('getJobspecialisims')
return store.dispatch('getJobspecialisims')
},
Source for this answer: Problem: Vuex actions, dispatch from page
Anyway, I still don't understand why you used await with then. If I, I will use async await like this:
async getJobspecialisims({ commit }) {
const res = await axios.get(YOUR_URL);
if (!res.error) {
commit("getJobspecialisims", res.data.data);
} else {
console.log(res.error);
}
}