Before I started to use .vue components I wrote a component that had the following code working successufuly
import { debounce } from '../utils/debounce';
Vue.component('play-list-manager', {
...
methods: {
renamePlayList: debounce((oldName, event) => {
store.dispatch(Actions.renamePlayList(oldName, event.target.value));
}, 500),
},
template: `<input
type="text"
v-bind:value="tag.name"
v-on:keyup="renamePlayList(tag.name, $event)"
placeholder="... playlist name">`
});
but when I switched to .vue components it does not work anymore
The problem is that the input of debounce it not ('some name', Event) but it now is (function(oldName, event), 500). So it's getting the input of debounce as input.
What is the correct way to add a debounce function.
Just for completion here is my debounce function
export function debounce(func, wait) {
// we need to save these in the closure
let timeout;
let args;
let context;
let timestamp;
return () => {
// save details of latest call
context = this;
args = [].slice.call(arguments, 0);
timestamp = new Date();
// this is where the magic happens
const later = () => {
// how long ago was the last call
const last = (new Date()) - timestamp;
// if the latest call was less that the wait period ago
// then we reset the timeout to wait for the difference
if (last < wait) {
timeout = setTimeout(later, wait - last);
// or if not we can null out the timer and run the latest
} else {
timeout = null;
func.apply(context, args);
}
};
// we only need to set the timer now if one isn't already running
if (!timeout) {
timeout = setTimeout(later, wait);
}
};
}
I found the solution, though I do not understand it completely
export function debounce(func, wait) {
// we need to save these in the closure
let timeout;
let args;
let context;
let timestamp;
return function() {
// save details of latest call
context = this;
args = [].slice.call(arguments, 0);
timestamp = new Date();
// this is where the magic happens
const later = () => {
// how long ago was the last call
const last = (new Date()) - timestamp;
// if the latest call was less that the wait period ago
// then we reset the timeout to wait for the difference
if (last < wait) {
timeout = setTimeout(later, wait - last);
// or if not we can null out the timer and run the latest
} else {
timeout = null;
func.apply(context, args);
}
};
// we only need to set the timer now if one isn't already running
if (!timeout) {
timeout = setTimeout(later, wait);
}
};
}
Related
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.
I am building a Timer to run in the background
I am Using React Native and Expo to build the app
Now the timer I am using I am trying to let it "run" in the background
To do this I am using AppState and a event listner, taking the Start Time, and the elapsed Time (Elabsed while the app is minimized)
And then recalculating the Time elapsed and adding that to the timer
In the Timer There are start, pause, reset and done buttons
Want I am not getting to work is that the elapsed time must not get set into the setstate if the pause button is clicked, and no matter how many IFs I put in there, the Time always gets changed
const appState = useRef(AppState.currentState);
const [timerOn, setTimerOn] = useState(false);
const [time, setTime] = useState(0);
const [Paused,isPaused] = useState("");
const getElapsedTime = async () => {
try {
const startTime = await AsyncStorage.getItem("#start_time");
const now = new Date();
return differenceInSeconds(now, Date.parse(startTime));
} catch (err) {
console.warn(err);
}
};
const recordStartTime = async () => {
try {
const now = new Date()
await AsyncStorage.setItem("#start_time", now.toISOString());
} catch (err) {
console.warn(err);
}
};
useEffect(() => {
Timer()
}, []);
function Timer(){
if(Paused == "no"){
AppState.addEventListener("change", handleAppStateChange);
return () => AppState.removeEventListener("change", handleAppStateChange);
}
else{
console.log("eVENT lISTNER")
}
}```
const handleAppStateChange = async (nextAppState) => {
if (appState.current.match(/inactive|background/) &&
nextAppState == "active" && Paused == "no") {
// We just became active again: recalculate elapsed time based
// on what we stored in AsyncStorage when we started.
const elapsed = await getElapsedTime();
// Update the elapsed seconds state
//THE BELOW STATE IS UPDATED TO "ELAPSED" EVENTHOUGH IN THE IF STATEMENT ABOVE SAYS "PAUSED = NO"
setTime(elapsed);
}
else{
console.log("YES")
}
appState.current = nextAppState;
};
useEffect(() => {
let interval = null;
if (timerOn) {
interval = setInterval(() => {
setTime((prevTime) => prevTime + 1);
}, 1000);
} else if (!timerOn) {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [timerOn]);
let MyHour = ("0" + Math.floor((time / 3600))).slice(-2);
let MyMinutes =("0" + Math.floor((time / 60) % 60)).slice(-2);
let MySeconds = ("0" + ((time % 60))).slice(-2);
function TimerBtn(){
isPaused("no")
setTimerOn(true)
recordStartTime()
}
function PauseBtn(){
isPaused("yes")
setTimerOn(false)
}
function ResetBtn(){
setTimerOn(false)
setTime(0)
}
function DoneBtn(){
}
Your problem is probably that you cannot access the current state of Paused inside a listener. Read this answer here for a detailed explanation
To work around, use a reference instead (useRef).
const [Paused,isPaused] = useState("");
to
const isPaused = useRef(true); // use booleans instead of "yed"/"no"
keep in mind to read and write to references you have to append '.current' to the reference, e.g:
isPaused.current = true;
// or
if (isPaused.current) {...}
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.
I have v-text-field
<v-text-field
name="name"
label="B/F/L Cost"
prefix="$"
#input="throttledSave"
v-model="$store.state.campaign.MyCost"
></v-text-field>
methods: {
save(val) {
console.log(val);
},
throttledSave(val) {
let DELAY = 5000;
return this.throttle(this.save(val), DELAY);
},
throttle(callback, limit) {
console.log("throttle");
var wait = false;
return function() {
if (!wait) {
console.log("throttle");
callback.call();
wait = true;
setTimeout(function() {
wait = false;
}, limit);
}
};
}
}
I need to add listener for text changed. When i have chenget the v-text-field my UI is freezing. How i can add listener to v-text-field without freezing?
"I think" there're some problems with your code:
You set your vue-x state directly to v-model v-model="$store.state.campaign.MyCost", it's bad practice
Your throttledSave method seems to not do what you want it to do, it calls save immediately and return the method throttle's execution (which executes nothing, then return a function without executing it anywhere).
The callback parameter of your throttle method actually got the return value of this.save instead of the function this.save itself, so it can't be called like callback.call(); (call is not a method of undefined)
I also suspect the logic of your throttle method
save(val) {
console.log(val); // this doesn't return a function, return undefined
},
throttledSave(val) {
let DELAY = 5000;
return this.throttle(this.save(val), DELAY); // you called this.save immediately, got undefined
},
throttle(callback, limit) {
console.log("throttle");
var wait = false;
return function() { // this function is returned, but will not be executed anywhere
if (!wait) {
console.log("throttle");
callback.call(); // callback is passed in throttledSave is not a function, it's the return result of a function (which this.save's is undefined)
wait = true;
setTimeout(function() { // I suspect the logic of your throttle
wait = false;
}, limit);
}
};
}
Solution: What you might want in throttledSave is:
computed: {
DELAY = () => 5000,
},
methods: {
throttledSave: this.throttle(function(val) { // make sure to write the throttle method correctly
this.save(val);
}, this.DELAY /* or 5000 directly instead */),
}
You might want to use lodash's throttle instead of writing one from scratch yourself.
import _ from 'lodash';
...
methods: {
save(val) {
console.log(val);
},
throttledSave: _.throttle(this.save, 5000),
// or:
throttledSave: _.throttle(function(val) {
console.log(val);
}, 5000),
}
That's a tricky one I guess. I'm prompting user for a word which I validate with an Axios API call. Once validation clears, the main loop of my game - hangman - starts with a wait between each move (hence the use of await).
Problem: in current version, the main game loop (starting after "once validation clears, game starts below" comment) must starts after validation, when in fact it starts at the same time which messes everything up.
And I can't put my main loop inside the then() part of my Axios call because in that case the await-ed function call ceases to work.
Any idea to get out of this mess?
async startGameComputerGuesser () {
var wordIsValidated = false
const vm = this
const dispatcher = {
execute: function () {
const wordApiBaseUrl = 'https://www.dictionaryapi.com/api/v1/references/sd4/xml'
wordToGuess = prompt('Enter a word:').toLowerCase()
const dispatcher = this
vm.axios.get(`${wordApiBaseUrl}/${wordToGuess}?key=${wordApiKey}`).then(res => {
if (!res.data.includes('def')) {
dispatcher.execute()
} else {
wordIsValidated = true
}
})
}
}
dispatcher.execute()
// once validation clears, game starts below
if (wordIsValidated) {
while (!this.$store.state.gameIsOver) {
await this.resolveAfter2Seconds()
// main loop of the game goes here
}
}
}
use await inside the execute and return true/false then use while to check that condition like below
async startGameComputerGuesser() {
let wordIsValidated = false;
const vm = this;
const dispatcher = {
async execute() {
const wordApiBaseUrl = 'https://www.dictionaryapi.com/api/v1/references/sd4/xml'
const wordToGuess = prompt('Enter a word:').toLowerCase();
const res = await vm.axios.get(`${wordApiBaseUrl}/${wordToGuess}?key=${wordApiKey}`);
return res.data.includes('def');
}
}
// validation
while (!wordIsValidated) {
wordIsValidated = await dispatcher.execute();
}
// game starts below
while (!this.$store.state.gameIsOver) {
await this.resolveAfter2Seconds()
// main loop of the game goes here
}
}
Example code:
const startGameComputerGuesser = async function() {
let wordIsValidated = false;
const dispatcher = {
async execute() {
const res = await new Promise(res => setTimeout(() => res(Math.floor(Math.random() * 10)), 500));
console.log(res);
return res == 6;
}
}
// validation
while (!wordIsValidated) {
wordIsValidated = await dispatcher.execute();
}
// once validation clears, game starts below
console.log('started');
}
startGameComputerGuesser();