why does async inside computed make infinity loops (vue)? - vue.js

My computed of component like this :
export default {
computed: {
dataDoctorPerPage: async function () {
const start = this.pagination.page * this.pagination.rowsPerPage - this.pagination.rowsPerPage;
const end = start + this.pagination.rowsPerPage - 1;
const doctors = this.dataDoctor
const newDoctors = {}
let key = 0
for(let item in doctors) {
if(key >= start && key <= end) {
for (let i = 0; i < doctors[item].length; i++) {
const params = {
hospitalId: doctors[item][i].hospital_id,
doctorId: doctors[item][i].doctor_id,
}
await this.getDataSchedule(params) /* call async */
// console.log(this.dataSchedule)
}
newDoctors[item] = doctors[item]
}
key++
}
return newDoctors
}
}
}
If the dataDoctorPerPage called it will run the script
await this.getDataSchedule(params) will call async/api by vuex store. My problem is there. when I call await this.getDataSchedule(params), it will loop without stopping
My vuex store like this :
const actions = {
async getDataSchedule ({ commit }, payload) {
const result = await api.getDataSchedule(payload)
const items = result.data
commit('setDataSchedule', { items: items })
},
}
How can I solve this problem?
Whether there can not run async in computed?

Computed should not use async. If you want to do that, you need another library for it. https://alligator.io/vuejs/async-computed-properties/
But what you want to do is use an asynchronous method (in the component or store) and set the data somewhere (in the component's data or store's state), then have your computed value reference that.

Related

How to make pagination work? Async await function in vue.js 3 setup

I was trying to make an app which lists a user's repositories from github using github API, however I'm having a big problem with fetching data from all pages (so far I can only get repos from one page). I tried to fix it by using an async/await function (instead of Promise), but it's also my first time using vue3 and I have no idea how to have a function inside of the setup() method.
The current code is here:
https://github.com/agzpie/user_repos
My try at using async/await, which didn't work:
import ListElement from "./components/ListElement";
import { ref, reactive, toRefs, watchEffect, computed } from "vue";
export default {
name: "App",
components: {
ListElement,
},
setup() {
const name = ref(null);
const userName = ref(null);
const state = reactive({ data: [] });
let success = ref(null);
const userNameValidator = /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i;
const split1 = reactive({ spl1: [] });
const split2 = reactive({ spl2: [] });
async function myFetch() {};
/*
* Check for input in the form and then fetch data
*/
watchEffect(() => {
if (!userName.value) return;
if (!userNameValidator.test(userName.value)) {
console.log("Username has invalid characters");
return;
}
let hasNext = false;
state.data = [];
do {
async function myFetch() {
let url = `https://api.github.com/users/${userName.value}/repos?per_page=5`;
let response = await fetch(url);
if (!response.ok) {
success.value = false;
throw new Error(`HTTP error! status: ${response.status}`);
}
success.value = true;
// check response.headers for Link to get next page url
split1.spl1 = response.headers.get("Link").split(",");
let j = 0;
while (j < split1.spl1.length) {
split2.spl2[j] = split1.spl1[j].split(";");
console.log(split2.spl2[j][0]);
console.log(split2.spl2[j][1]);
if (split2.spl2[j][1].includes("next")) {
let urlNext = split2.spl2[j][0].replace(/[<>(\s)*]/g, "");
console.log(urlNext);
url = urlNext;
hasNext = true;
break;
} else {
hasNext = false;
}
j++;
}
// second .then
let myData = await response.json();
state.data.push(...myData);
console.log("data", myData);
name.value = "";
}
myFetch().catch((err) => {
if (err.status == 404) {
console.log("User not found");
} else {
console.log(err.message);
console.log("oh no (internet probably)!");
}
});
} while (hasNext);
});
// Sort list by star count
const orderedList = computed(() => {
if (state.data == 0) {
return [];
}
return [...state.data].sort((a, b) => {
return a.stargazers_count < b.stargazers_count ? 1 : -1;
});
});
return {
myFetch,
success,
isActive: true,
name,
userName,
ListElement,
...toRefs(state),
orderedList,
};
},
};
Any help would be highly appreciated
The call to myFetch() near the end is a call to an async function without an await, so it is effectively going to loop (if hasNext was initialized to true, but it isn't) without waiting for it to complete.
You should probably change that line to await myFetch() and wrap it all with a try/catch block.
I also don't really care for the way you're directly updating state inside the async myFetch call (it could also be doing several of those if it looped) and perhaps it should be returning the data from myFetch instead, and then you can use let result = await myFetch() and then make use of that when it returns.
Also, instead of awaiting myFetch() result, you could not await it but push it onto a requests array and then use await Promise.all(requests) outside the loop and it is one operation to await, all requests running in parallel. In fact, it should probably be await Promise.allSettled(requests) in case one of them fails. See allSettled for more.
But also I wonder why you're reading it paged if the goal is to fetch them all anyway? To reduce load on the server? If that is true, issuing them paged but in parallel would probably increase the load since it will still read and return all the data but require multiple calls.

strange console.log output with vuex

i have some simple vuex store with
const state = {
todos : []
}
const getters = {
allTodos: (state) => state.todos
}
const actions = {
async fetchTodos({ commit }) {
console.log(this.state.todos)
if(state.todos.length == 0) {
const response = await axios.get('https://jsonplaceholder.typicode.com/todos?_limit=5')
commit('setTodos', response.data)
}
}
}
const mutations = {
setTodos(state, todos) {
state.todos = todos
}
}
why does console.log in fetchTodos action output populated todos before it was populated with axios.get and setTodos mutation?
when i write
const actions = {
fetchTodos({ commit }) {
console.log(this.state.todos)
setTimeout(async () => {
if(state.todos.length == 0) {
const response = await axios.get('https://jsonplaceholder.typicode.com/todos?_limit=5')
commit('setTodos', response.data)
}
}, 10000)
}
}
output is normal with empty todos in state
That's because you will see a little blue triangle right next to the console log. I don't know the technical term for it but what happens is that the browser will update that variable with the current value because it is a reactive variable and since it is a reference being pointed to a location in memory, it will update.
If you truly wish to see the value and prove what was described above, you can write:
console.log(JSON.parse(JSON.stringify(this.state.todos)));

Vue 2 composition API watching the store

I have a store which is just an array of strings.
I am trying to watch it and do a search when it has changed.
Originally I had a computed value a bit like this:
const { value } = computed(() => {
const urls = store.getters.wishlist;
filters.value = createFilters("IndexUrl", urls);
return useListProducts(page.value, filters.value);
});
which I returned like this:
return { ...value, skip, more };
This worked fine when loading the page the first time, but if another component adds/removes something from the wishlist I want the function to fire again.
For context, here is the whole component:
import {
computed,
defineComponent,
getCurrentInstance,
ref,
} from "#vue/composition-api";
import Product from "#components/product/product.component.vue";
import {
createFilters,
createRequest,
useListProducts,
} from "#/_shared/logic/list-products";
export default defineComponent({
name: "Wishlist",
components: { Product },
setup() {
const instance = getCurrentInstance();
const store = instance.proxy.$store;
const page = ref(1);
const skip = ref(0);
const filters = ref([]);
const { value } = computed(() => {
const urls = store.getters.wishlist;
filters.value = createFilters("IndexUrl", urls);
return useListProducts(page.value, filters.value);
});
const more = () => {
skip.value += 12;
page.value += 1;
const request = createRequest(page.value, filters.value);
value.fetchMore({
variables: { search: request },
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return {
search: {
__typename: prev.search.__typename,
hasMoreResults: fetchMoreResult.search.hasMoreResults,
total: fetchMoreResult.search.total,
facets: [...prev.search.facets, ...fetchMoreResult.search.facets],
items: [...prev.search.items, ...fetchMoreResult.search.items],
},
};
},
});
};
return { ...value, skip, more };
},
});
So I figured that the issue was that I wasn't actually watching anything, so I removed the computed method and instead decided to setup a watch. First I created a listProducts method:
const result = reactive({
result: null,
loading: null,
error: null,
fetchMore: null,
});
const listProducts = (urls: string[]) => {
console.log(urls);
filters.value = createFilters("IndexUrl", urls);
Object.assign(result, useListProducts(page.value, filters.value));
};
And then I invoked that in my setup:
listProducts(store.getters.wishlist);
Then I setup a watch:
watch(store.getters.wishlist, (urls: string[]) => listProducts(urls));
What I expected to happen, was that when an item was added/remove from the wishlist store, it would then invoke listProducts with the new set of urls. But it didn't fire at all.
Does anyone know what I am doing wrong?
I believe the issue is with destructuring the reactive property, on destructuring you assign the properties to variables and no longer have a proxy to react to changes..try
return { value, skip, more };
and reference the property in your template
<template>
{{value.foo}}
</template
this question has to do with setup props but the same concept applies
Vue 3 watch doesn’t work if I watch a destructured prop

Vue.js - control the router from inside a mutation in Vuex Store

I have the following problem:
This is one of my mutations:
tryAutoLogin(state) {
console.log("Trying auto login...");
const token = window.localStorage.getItem("token");
if (!token) {
return;
}
const expirationDate = window.localStorage.getItem("token_exp");
const now = new Date();
if (now >= expirationDate) {
return;
}
state.userData.loggedIn = true;
state.userData.username = token.identity;
// desired: this.$router.push("/dashboard") => results in undefined
}
Currently I commit this mutation inside my component in the created phase of the component:
created() {
this.$store.commit("tryAutoLogin");
this.$router.push("/dashboard");
}
This is not a great way to do it, since I would have to output a value, store it in a variable and use if/else to this this.$router.push("/dashboard").
How can I solve this in an elegant way? Favorable inside the mutation like in the // desired comment. Is there a way to access the router inside the Vuex store?
Pass the vue component instance to the mutation like:
this.$store.commit("tryAutoLogin",this);
in mutation add it as parameter vm then use it as vm.$router.push("/dashboard") :
tryAutoLogin(state,vm) {
console.log("Trying auto login...");
const token = window.localStorage.getItem("token");
if (!token) {
return;
}
const expirationDate = window.localStorage.getItem("token_exp");
const now = new Date();
if (now >= expirationDate) {
return;
}
state.userData.loggedIn = true;
state.userData.username = token.identity;
vm.$router.push("/dashboard")
}

How to exit a while loop depending of the result of an Axios call?

In order to make sure a user-entered word exists, I need to keep prompting (while loop) for a word until the word API finds the word.
My question is: how can I exit the while loop depending of Axios call result?
Below is my code so far.
const wordApiBaseUrl = 'https://www.dictionaryapi.com/api/v1/references/sd4/xml'
while (true) {
const wordToGuess = prompt('Enter a word:').toLowerCase()
const endPointUrl = `${wordApiBaseUrl}/${wordToGuess}?key=${wordApiKey}`
this.axios.get(endPointUrl).then(res => {
if (res.data.includes('def')) {
break
}
})
}
Try this:
const wordApiBaseUrl = 'https://www.dictionaryapi.com/api/v1/references/sd4/xml'
const vm = this; // <--- assuming this is the Vue instance
const dispatcher = {
execute: function() {
const wordToGuess = prompt('Enter a word:').toLowerCase()
const endPointUrl = `${wordApiBaseUrl}/${wordToGuess}?key=${wordApiKey}`
const dispatcher = this;
vm.axios.get(endPointUrl).then(res => {
if (!res.data.includes('def')) {
dispatcher.execute();
}
})
}
}
dispatcher.execute();
Rather than using a while loop or using an async/await you can use recursion in our promise. If the result is not satisfied, re-run the AJAX call.