React : Trigger function when the user stops typing - reactjs

I am using react-select with async create table and implemented this to Netsuite custom page. The problem is that I want to load the getAsyncOptions function to trigger only when the user stops typing. At the moment these line of codes make the input field so slow as the function is getting triggered everytime a letter is added to the input. I also cant insert the fetched data to the state because it will be populated with a thousand records.
Email.tsx
getAsyncOptions(inputValue) {
return new Promise((resolve) => {
var typingTimer; //timer identifier
var doneTypingInterval = 1000; //time in ms (1 second)
if (inputValue.length <= 3) {
return resolve([]);
}
clearTimeout(typingTimer);
if (inputValue) {
return (typingTimer = setTimeout(function () {
var t0 = performance.now();
emailSearch(inputValue).then((data) => {
const returnData = [];
data.forEach((contact) => {
returnData.push({
label: contact.email + " - (" + contact.company + ")",
value: contact.email,
});
});
resolve(returnData);
console.log(
"Call to email search function took " +
(t1 - t0) +
" milliseconds."
);
});
var t1 = performance.now();
}, doneTypingInterval));
}
});
As you see on the above code the it is only delaying the code to trigger. And another problem arises when the user stopped typing for 1 second and continue to type again, it is just delaying the function, and being triggered every 1 second. Here's the rest of the code for your reference.
RenderOpenMode.tsx
<AsyncCreatableSelect
value={props.state.toEmailCollection}
onChange={props.setToEmail}
loadOptions={props.debouncedLoadQuery}
styles={props.customSelectStylesEmail}
components={{
DropdownIndicator: () => null,
IndicatorSeparator: () => null,
}}
isMulti={true}
isSearchable={true}
isValidNewOption={props.isValidNewOption}
placeholder=""
/>
The Functions
this.setToEmail = (toEmailCollection) =>
this.setState({ toEmailCollection: toEmailCollection });
this.setToCc = (toCcCollection) =>
this.setState({ toCcCollection: toCcCollection });
const loadOptions = (inputValue) => this.getAsyncOptions(inputValue));
this.debouncedLoadQuery = debounce(loadOptions, 2000, {
leading: true,
});
Been facing this roadblock for a while and any idea or help would be very much appreciated! Thank you so much and God bless!
Edit:
Added some code. The onChange only updates some state, the problem is the loadOptions as it is the one that is triggering the getAsyncOptions.

I've fixed the issue, it seems that the problem is on the debounce. I am using debounce-promise and the problem is that I've used leading=true option and that makes the UI unresponsive on change.
From this code:
this.debouncedLoadQuery = debounce(loadOptions, 2000, {
leading: true,
});
To this code:
this.debouncedLoadQuery = debounce(loadOptions, 2000);

Related

React multiple console.log?

Im creating a function that sends an email based on the expiry date, however, upon trial, the function works fine but the email gets sent multiple times and the console.log is shown multiple times. Does anyone know how to counteract?
if (product?.expiry) {
var arr = []
var today = new Date()
var expiry = new Date(product?.expiry)
var week = new Date(expiry.setDate(expiry.getDate() - 7))
var template = {
message: product.name + " is reaching expiry date",
footer: "Expiry Date: " + week.toDateString()
}
const interval = setInterval(() => {
if (arr.length > 0) {
clearInterval(interval)
console.log(arr)
}
if (today !== expiry) {
// actual emailing
emailjs.send(SERVICE_API, TEMPLATE_API, template, PUBLIC_API)
.then(function(response) {
console.log('SUCCESS!', response.status, response.text);
}, function(error) {
console.log('FAILED...', error)
});
arr.push('found')
}
}, 1000)
}
ALERT: I am aware that the function is sending the email when it is not the expiry date, i just want to fix up this console.log error
**rest of function: **
const expiryEmail = () => {
if (today !== week) {
emailjs.send(SERVICE_API, TEMPLATE_API, template, PUBLIC_API)
.then(function(response) {
console.log('SUCCESS!', response.status, response.text);
}, function(error) {
console.log('FAILED...', error)
});
}
}
setTimeout(() => {
})
useEffect(() => {
if (!product) {
return;
}
//problem is that the condition returns null first time around
expiryEmail()
},[])
Try to use useEffect hook instead to set interval I am sharing the code try to use that
var today = new Date()
var expiry = new Date(product?.expiry)
var week = new Date(expiry.setDate(expiry.getDate() - 7))
var template = {
message: product.name + " is reaching expiry date",
footer: "Expiry Date: " + week.toDateString()
}
useEffect(()=>{
if (today !== expiry) {
emailjs.send(SERVICE_API, TEMPLATE_API, template, PUBLIC_API)
.then(function(response) {
console.log('SUCCESS!', response.status, response.text);
}, function(error) {
console.log('FAILED...', error)
});
arr.push('found')
}
},[])
The first condition inside your setInterval method's callback, ie.
if (arr.length > 0) {
clearInterval(interval)
console.log(arr)
}
does not have a return statement, which means it will continue on to your next if block, whose condition is satisfied because you are checking is today's date is not the same as expiry 's date, which as you alerted is a conscious decision, and therefore print the console message again.
Also, I hope by multiple you mean only twice, because that is what my answer applies for, if you meant more than that, I'd like to see the part that calls this method.
Again also, you should use Mayank's suggestion and process your code inside a useEffect block, much simpler.

ReactJS: Why does navigating appear to change `readyState` of previous `EventSource`s?

Question: Why does navigating appear to change the readyState of the previous EventSources?
===============================================
Explanation: I'm working on a frontend (React) in which the user can enter a sequence of search queries (i.e. strings) and for each search query, my backend (Flask) will return a sequence of URLs. For each search query, I've decided to receive the server's response via an EventSource. Specifically, I first create a React state array of backendEventSources:
const [backendEventSources, setBackendEventSources] = useState([]);
Then I update the backendEventSources when a new prompt comes in:
useEffect(() => {
console.log('Inside useEffect')
// Take 0 for the newest prompt.
const newBackendEventSource = new EventSource(
`https://localhost:8080/generate?counter=${promptsResultsArray[0].counter}&prompt=${promptsResultsArray[0].prompt}`,
{withCredentials: false})
newBackendEventSource.addEventListener('open', () => {
console.log('SSE opened!');
});
newBackendEventSource.addEventListener('error', (e) => {
console.log('SSE error!');
console.error('Error: ', e);
});
newBackendEventSource.addEventListener('close', (e) => {
console.log('SSE closed!');
const data = JSON.parse(e.data);
console.log("close data: ", data);
newBackendEventSource.close();
});
newBackendEventSource.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log("message data: ", data);
// https://stackoverflow.com/a/47580775/4570472
const newPromptsResultsArray = [...promptsResultsArray];
// Since we preprend new results, we need to compute the right index from
// the counter with the equation: length - counter - 1.
// e.g., For counter 2 of a length 3 array, we want index 0.
// e.g., For counter 2 of a length 4 array, we want index 1.
// Recall, the counter uses 0-based indexing.
const index = newPromptsResultsArray.length - data.counter - 1
newPromptsResultsArray[index].URIs = [data.uri];
newPromptsResultsArray[index].isLoading = false;
setPromptsResultsArray(newPromptsResultsArray);
// Instantiating the element and setting the src property starts preloading the image.
// for (const newImgURI of newImgURIs) {
// const imageElement = new Image();
// imageElement.src = newImgURI;
// }
// setTimeout(() => {setImgURIs(newImgURIs)}, 8000);
});
// Add new backend event source to state for persistence.
setBackendEventSources(backendEventSources => [
newBackendEventSource,
...backendEventSources])
return () => {
newBackendEventSource.close();
};
}, [prompt]);
I use URL params for React navigation:
const navigateToGenerateResults = (promptString) => {
console.log('Adding new prompt results to promptsResultsArray');
// State doesn't update immediately (or even synchronously). To ensure we can immediately
// access the new values, we create a newPromptsResults.
// https://stackoverflow.com/a/62900445/4570472
const newPromptsResults = {
isLoading: true,
prompt: promptString,
counter: promptsResultsArray.length,
URIs: ["https://simtooreal-public.s3.amazonaws.com/white_background.png"]
}
// Prepend the new prompt to our promptsResultsArray
// https://stackoverflow.com/a/60792858/4570472
setPromptsResultsArray(promptsResultsArray => [
newPromptsResults, ...promptsResultsArray])
console.log('Setting prompt to: ' + newPromptsResults.prompt)
setPrompt(newPromptsResults.prompt)
console.log('Navigating from /generate to /generate with prompt: ' + newPromptsResults.prompt)
navigate(`/generate?counter=${newPromptsResults.counter}&prompt=${newPromptsResults.prompt}`)
}
However, I've discovered that as soon as I navigate from one URL to another, the previous EventSource's ready state switches from 0/1 to 2. Additionally, my newBackendEventSource.addEventListener('close' function is never triggered.
Why does navigating appear to change the readyState of the previous EventSources?

Discord poll bot async issues

I am trying to make a poll command for a discord bot in which the user chooses a number of options in the first command (ie '!poll 4') and then chooses the questions and the options. I am having some issues getting the bot to wait for a response before it moves on to the next option in the loop. When I try and use await in the loop it says I cannot use await because it's not an async function, but it is an async function I think. I am very inexperienced with this so I am sure it is a simple error or probably multiple. If anyone can give me advice on a way to make the loop work as intended and ask for each option I would appreciate it. Also is there a way to add if statements to do addFields to an embed? Here is my code:
const Discord = module.require('discord.js');
module.exports = {
name: 'poll',
async execute(message, args) {
function isNumber(n) { return !isNaN(parseFloat(n)) && !isNaN(n - 0) }
if(isNumber(args[1])){
if(args[1]<2) return message.channel.send('Please choose a higher number of options for the poll :)');
if(args[1]>10) return message.channel.send('Please choose a lower number of options for the poll :)');
const filter = response => {
if(!response.author.bot) return response;
};
var question;
var options;
message.channel.send('What question would you like to ask?').then(() => {
message.channel.awaitMessages(filter, { max: 1, time: 15000})
.then(collected => {
question = `${collected.first()}?`;
message.channel.send('Question: ' + question);
for (var i = 0; i < args[1]; i++) {
message.channel.send('What is option ' + (i + 1) + '?').then(() => {
message.channel.awaitMessages(filter, { max: 1, time: 15000})
.then(collected => {
options[i] = collected.first;
message.channel.send(`Option ${i}: ${options[i]}`);
})
.catch(collected => {
message.channel.send('Poll has timed out.');
});
})
}
})
.catch(collected => {
message.channel.send('Poll has timed out.');
});
const embed = new Discord.MessageEmbed()
.setColor(3447003)
.setTitle(question)
.setDescription('choose an option')
/*
if (options[0]) .addField('1️⃣:' + option[0])
if (options[1]) .addField('2️⃣:' + option[1])
if (options[2]) .addField('3️⃣:' + option[2])
if (options[3]) .addField('4️⃣:' + option[3])
if (options[4]) .addField('5️⃣:' + option[4])
if (options[5]) .addField('6️⃣:' + option[5])
if (options[6]) .addField('7️⃣:' + option[6])
if (options[7]) .addField('8️⃣:' + option[7])
if (options[8]) .addField('9️⃣:' + option[8])
if (options[9]) .addField('🔟:' + option[9])
*/
message.channel.send(embed).then(embedMessage => {
if (options[0]) embedMessage.react('1️⃣');
if (options[1]) embedMessage.react('2️⃣');
if (options[2]) embedMessage.react('3️⃣');
if (options[3]) embedMessage.react('4️⃣');
if (options[4]) embedMessage.react('5️⃣');
if (options[5]) embedMessage.react('6️⃣');
if (options[6]) embedMessage.react('7️⃣');
if (options[7]) embedMessage.react('8️⃣');
if (options[8]) embedMessage.react('9️⃣');
if (options[9]) embedMessage.react('🔟');
});
});
}
}
}
Since you say you are trying to use await in your loop, let me take the function it is contained in out from your snippet, format it a little, and try to do some explaining. Disclaimer: I am no expert, so I am learning as well.
.then(collected => {
question = `${collected.first()}?`;
message.channel.send(`Question: ${question}`);
for (var i = 0; i < args[1]; i++) {
message.channel.send(
`What is option ${i + 1}?`
).then(() => {
message.channel.awaitMessages(filter, {
"max": 1,
"time": 15000,
}).then(collected => {
options[i] = collected.first;
message.channel.send(`Option ${i}: ${options[i]}`);
}).catch(collected => {
message.channel.send("Poll has timed out.");
});
});
}
});
But before that, as the first inner .then() still returns a Promise, you can chain the second inner .then() in the outer scope to avoid nesting them too deep, and leave a single .catch() at the end. On that note, it would probably be more accurate to call the catch's parameter something like error. So here's the new snippet:
.then(collected => {
question = `${collected.first()}?`;
message.channel.send('Question: ' + question);
for (var i = 0; i < args[1]; i++) {
message.channel.send(
`What is option ${i + 1}?`
).then(() => {
message.channel.awaitMessages(filter, {
"max": 1,
"time": 15000,
});
}).then(collected => { // Change .then() chaining
options[i] = collected.first;
message.channel.send(`Option ${i}: ${options[i]}`);
}).catch(error => { // Change parameter name
message.channel.send("Poll has timed out.");
});
}
})
What's happening now is that each iteration is running one after the other immediately. You .send() a whole bunch of messages which each return a Promise, and off that Promise, you pass a callback function to .then() which runs once each Promise resolves into a Message. That callback implicitly returns the result of .awaitMessages(), which is also a promise, and once that resolves, the next callback in the next .then() runs with the value of whatever the previous promise resolved to passed in as an argument, and so on.
Alright, so you want to the entire Promise chain to finish processing and resolve before proceeding to the next iteration, right? You can use the await keyword to suspend progress in the relevant anonymous function, until its associated promise-based operation either resolves or rejects. The catch is that that function has to be marked with the async keyword, and in your code, that is not actually the case, you are just making use of Promises and callback functions (regarding "but it is an async function I think"). So, let's add both the aforementioned keywords:
.then(async collected => { // Mark as async
question = `${collected.first()}?`;
message.channel.send('Question: ' + question);
for (var i = 0; i < args[1]; i++) {
await message.channel.send( // Wait for this entire Promise chain to resolve before proceeding
`What is option ${i + 1}?`
).then(() => {
message.channel.awaitMessages(filter, {
"max": 1,
"time": 15000,
});
}).then(collected => {
options[i] = collected.first;
message.channel.send(`Option ${i}: ${options[i]}`);
}).catch(error => {
message.channel.send("Poll has timed out.");
});
}
})
That should cause your desired behaviour, though my edits may have syntax errors as I have not ran it myself. If I got something wrong, please do comment.
You can read more here:
Using async-await
Using Promises

Unsubscribe from timer in rxjs

Hi I have a timer running which is like it should show a component for 30sec after every 10 seconds. My code is like this`
import { timer } from "rxjs";
componentWillReceiveProps(nextProps, nextState) {
console.log("RECEIVED PROPS");
if (this.props.venuesBonusOfferDuration === 0) {
this.subscribe.unsubscribe();
}
this.firstTimerSubscription;
this.secondTimerSubscription;
// if (this.state.isMounted) {
if (
nextProps.showBonusObj &&
(!nextProps.exitVenue.exitVenueSuccess || nextProps.enterVenues)
) {
// console.log("isMounted" + this.state.isMounted);
//if (this.state.isMounted) {
let milliseconds = nextProps.venuesBonusOfferDuration * 1000;
this.source = timer(milliseconds);
this.firstTimerSubscription = this.source.subscribe(val => {
console.log("hiding bonus offer");
this.firstTimerSubscription.unsubscribe();
this.props.hideBonusOffer();
this.secondTimerSubscription = bonusApiTimer.subscribe(val => {
console.log("caling timer" + val);
this.props.getVenuesBonusOffer(
this.props.venues.venues.id,
this.props.uid,
this.props.accessToken
);
});
});
//}
} else {
try {
if (this.secondTimerSubscription != undefined) {
this.secondTimerSubscription.unsubscribe();
console.log("secondTimer UNSUBSCRIBED");
}
if (this.firstTimerSubscription != undefined) {
this.firstTimerSubscription.unsubscribe();
console.log("firstTimer UNSUBSCRIBED");
}
} catch (error) {
console.log(
"error when removing bonusoffer timer" + JSON.stringify(error)
);
}
//}
}
}
`
Problem is if I try to unsubscribe this * this.firstTimerSubscription* and this.secondTimerSubscription like this
try {
if (this.secondTimerSubscription != undefined) {
this.secondTimerSubscription.unsubscribe();
console.log("secondTimerunmount UNSUBSCRIBED");
}
if (this.firstTimerSubscription != undefined) {
this.firstTimerSubscription.unsubscribe();
console.log("firstTimerunmount UNSUBSCRIBED");
}
} catch (error) {
console.log("error bonusoffer timer" + JSON.stringify(error));
}
its still prints logs within timer like "hiding bonus offer" and "calling timer".
Can someone please point out the issue. It been a day since am into this.
Any help is appreciated.
The problem is that you subscribe multiple times (whenever component receives props) and reassign newest subscription to firstTimerSubscription or secondTimerSubscription references. But doing that, subscriptions does not magically vanish. To see how it works here is a demo:
const source = timer(1000, 1000);
let subscribe;
subscribe = source.subscribe(val => console.log(val));
subscribe = source.subscribe(val => console.log(val));
setTimeout(() => {
subscribe.unsubscribe();
}, 2000)
Even though you unsubscribed, the first subscription keeps emiting. And the problem is that you lost a reference to it, so you can't unsubscribe now.
Easy fix could be to check whether you already subscribed and unsubscribe if so, before subscribing:
this.firstTimerSubscription ? this.firstTimerSubscription.unsubscribe: false;
this.firstTimerSubscription = this.source.subscribe(...
I wouldn't use a second timer. Just do a interval of 10 seconds. The interval emits the iteration number 1, 2, 3..... You can use the modulo operator on that tick. Following example code (for example with 1 second interval) prints true and false in console. After true it needs 3 seconds to show false. After false it needs 1 second to show true.
interval(1000).pipe(
map(tick => tick % 4 !== 0),
distinctUntilChanged(),
).subscribe(x => console.log(x));

Nested callback function (React native JS )

I have a couple of different functions that helps set different states of the component and i wish to run the functions in order of one another. I know that there are multiple posts on these but they seem to be mostly catered for running one function after the other but i have more than two functions that i need to execute in order
Desired order
1) Set state of starting and final destination
2) Run this.getDirections() (This function sets the state of arrOfPolylines which i desire to reset through resetRouteSelectionStatus())
3) Run resetRouteSelectionStatus()
4) After running these functions i wish to have an empty this.state.arrOfPolylines
Actual results
There is no error in the code but it isnt entering the resetRouteSelectionStatus() as none of the console log are printed. Can someone please guide me on the right path?
<Button
onPress={() => { //on button press set final destination and starting location
{
(this.state.tempDestination.longitude != null && this.state.tempStarting.longitude != null) &&
this.setState({
finalDestination: {
latitude: this.state.tempDestination.latitude,
longitude: this.state.tempDestination.longitude,
},
startingLocation: {
latitude: this.state.tempStarting.latitude,
longitude: this.state.tempStarting.longitude,
}
}, () => {
this.getDirections((this.state.startingLocation.latitude + "," + this.state.startingLocation.longitude), (this.state.finalDestination.latitude + "," + this.state.finalDestination.longitude),
() => {this.resetRouteSelectionStatus()});
}
);
}
}}
title="Determine Directions"
color="#00B0FF"
resetRouteSelectionStatus() {
console.log('entered reset route selection status function')
this.setState({arrOfPolyline: null }, () => {console.log("i should be null nd come first" + this.state.arrOfPolyline)}) ;
this.setState({ selectChallengeStatus: null });
this.setState({ userRouteSelectionStatus: null }); //when user click on button set seleection status to 0 so route options will be displayed again after generation new route
//this.setState({arrOfDirectionDetails: []}); // clear past direction details when user select generate route with new starting/ending location
// clear past poly lines when user has selected new routes
//console.log("everything has been reset");
}
async getDirections(startLoc, destinationLoc) {
let resp = await fetch(`https://maps.googleapis.com/maps/api/directions/json?origin=${startLoc}&destination=${destinationLoc}&key="KEY"&mode=driving&alternatives=true`)
let respJson = await resp.json();
let routeDetails = respJson.routes;
let tempPolyLineArray = [];
let tempDirArrayRoute = [];
//console.log(startLoc);
//console.log(destinationLoc);
for (i = 0; i < routeDetails.length; i++) // respJson.routes = number of alternative routes available
{
let tempDirArray = []; // at every new route, initalize a temp direction array
let points = Polyline.decode(respJson.routes[i].overview_polyline.points);
let coords = points.map((point, index) => {
return {
latitude: point[0],
longitude: point[1]
}
})
tempPolyLineArray.push(coords);
for (k = 0; k < routeDetails[i].legs[0].steps.length; k++) // for each route go to the correct route, enter legs ( always 0), get the number of instruction for this route
{
//console.log (routeDetails[i].legs[0].steps[k])
tempDirArray.push(routeDetails[i].legs[0].steps[k]) // push all instructions into temp direction array
//this.state.arrOfDirectionDetails.push(routeDetails[i].legs[0].steps[k]); // for each instruction save it to array
}
tempDirArrayRoute.push(tempDirArray); // at the end of each route, push all instructions stored in temp array as an array into state
}
this.setState({ arrOfDirectionDetails: tempDirArrayRoute });
this.setState({ arrOfPolyline: tempPolyLineArray });
//creating my html tags
let data = [];
let temptitle = "Route ";
for (let j = 0; j < routeDetails.length; j++) {
data.push(
<View key={j}>
<Button
title={temptitle + j}
onPress={() => this.updateUser(j)}
/>
</View>
)
}
this.setState({ routebox: data })
}
So a few things to note here:
I would extract the inline function for the onButtonPress function into an async function
Something along the lines of:
const onButtonPressHandler = async () => {
... put your code here
}
Then you'll modify your onButtonPress to something like:
<Button
onPress={this.onButtonPressHandler}
... rest of your props here
/>
You can then properly use async / await in the buttonPress handler.
The setState function is not a synchronous function. If you rely on the results right away you'll be disappointed.
Each time you call setState you could trigger a rerender.
I would instead merge all of your setState calls into a single at the end.
The getDirections function does not include the callback, while it should be:
async getDirections(startLoc, destinationLoc, callback) {
.....
this.setState({ routebox: data },callback());
}
or but not sure if it would be in order:
async () => {
await this.getDirections((this.state.startingLocation.latitude + "," + this.state.startingLocation.longitude), (this.state.finalDestination.latitude + "," + this.state.finalDestination.longitude) );
this.resetRouteSelectionStatus();
}
You might need edit the resetRouteSelectionStatus to be:
resetRouteSelectionStatus = async ()=>{

Resources