Using setTimeout function not working in React - javascript

am trying to use the setTimeout function if the user clicks on the button, I want it to display successfully for just 3sec, it displaying but it's not executing the 3sec time given. what am I doing wrong?
Here’s my code
const [message, setMessage] = useState('')
function handleSubmit (e) {
e.preventDefault()
emailjs.sendForm(process.env.SERVICE_ID,process.env.TEMPLATE_ID, form.current,process.env.PUBLIC_KEY)
.then(function(response) {
return setTimeout(setMessage("successFully sent"), 3000)
}, function(err) {
console.log('FAILED...', err);
});
}

setTimeout(func,n) executes func after n milliseconds.
If you want the message to be displayed during 3s,
Set the message immediately
Clear the message after 3s.
Inside your then callback,
setMessage('successfully sent');
setTimeout(()=>{ // may start timer before message is set.
setMessage('')
}, 3000);
However due the asynchronous nature of state updates, the setTimeout may be "set" before the success message, resulting in the message for being shown for, example 2.993 seconds.
A more precise solution would be:
handleSubmit=(evt)=> { // arrow functions preserve 'this' of component
// ....
.then((response)=> {
this.setState({message: 'Successfully sent'}, ()=> {
setTimeout(()=> { // start timer after the message is set
this.setState({message: ''});
}, 3000);
});
})
// ...
}
Here the 2nd callback argument of setState is run, after message is set.

you need to use a anonymous function because right now you're calling the function when using setTimeout
const [message, setMessage] = useState('')
function handleSubmit (e) {
e.preventDefault()
emailjs.sendForm(process.env.SERVICE_ID,process.env.TEMPLATE_ID, form.current,process.env.PUBLIC_KEY)
.then(function(response) {
return setTimeout(() => setMessage("successFully sent"), 3000)
}, function(err) {
console.log('FAILED...', err);
});
}

Related

stop current run of useEffect and start the next one

I was wondering if there is any way to break the current process of a UseEffect and have it start on the next render, like this
...
useEffect(() => {
SlowFunction(update);
}, [update]);
setUpdate(1)
// a bit of time passes but not long enough for the SlowFunction(1) to be done
setUpdate(2)
//when this is called and the useEffect runs, stop the SlowFunction(1) and run SlowFunction(2)
my updated personal function is called in the use effect like so,
const [update, setUpdate] = useState(0);
const [thisConst, setThisConst] = useState(0);
async function SlowFunction(firstParam, paramEtc, { signal } = {}) {
while (true) {
//wait two seconds between each
await new Promise((r) => setTimeout(r, 2000));
// Before starting every individual "task" in the function,
// first throw if the signal has been aborted. This will stop the function
// if cancellation occurs:
signal?.throwIfAborted();
// else continue working...
console.log('working on another iteration');
}
console.log('Completed!');
return 'some value';
}
useEffect(() => {
const controller = new AbortController();
const { signal } = controller;
(async () => {
try {
const result = await SlowFunction(update, 'some other value', {
signal,
});
setConst(result);
} catch (ex) {
console.log('EXCEPTION THROWN: ', ex);
}
})();
return () => controller.abort(new Error('Starting next render'));
}, [update]);
The AbortSignal API is the standard method for handling cancellation.
I'll provide an example of how to use it with a function like your SlowFunction. You'll need to accept an abort signal as an optional parameter so that when the next render occurs, the function can be cancelled.
Here's an example cancellable function:
async function SlowFunction (firstParam, paramEtc, {signal} = {}) {
for (let i = 0; i < 1_000_000; i += 1) {
// Before starting every individual "task" in the function,
// first throw if the signal has been aborted. This will stop the function
// if cancellation occurs:
signal?.throwIfAborted();
// else continue working...
console.log('working on another iteration');
}
return 'some value';
}
You can use it in an effect hook like this: returning a cleanup function which invokes the abort method on the controller:
useEffect(() => {
const controller = new AbortController();
const {signal} = controller;
(async () => {
try {
const result = await SlowFunction(update, 'some other value', {signal});
setConst(result);
}
catch (ex) {
// Catch the exception thrown when the next render starts
// and the function hasn't completed yet.
// Handle the exception if you need to,
// or do nothing in this block if you don't.
}
})();
return () => controller.abort(new Error('Starting next render'));
}, [update]);
If the function completes before the next render occurs, then the abort operation will have no effect, but if it hasn't yet, then the next time that the statement signal?.throwIfAborted(); is reached, the function will throw an exception and terminate.
Update in response to your comment:
If your JavaScript runtime is too old to support the AbortSignal.throwIfAborted() method, you can work around that by replacing that line:
signal?.throwIfAborted();
with:
if (signal?.aborted) {
throw signal?.reason ?? new Error('Operation was aborted');
}

How to wait my custom command finish, then executing remaining Cypress commands

I'm getting trouble with Cypress asynchronous mechanism. I have a custom command that is placed in this file
class HeaderPage {
shopLink = 'a[href="/angularpractice/shop"]'
homeLink = ''
navigateToShopPage() {
cy.get(this.shopLink).click()
}
sshToServer() {
setTimeout(() => {
console.log('Connecting')
}, 5000)
console.log('Connected')
}
}
export default HeaderPage
The function sshToServer is simulated to pause 5000ms. So I want Cypress remaining test will be hold and wait for this function completed. How can I do that? Thanks in advance.
import HeaderPage from "../support/pageObjects/HeaderPage"
describe('Vefiry Alert and Confirm box', () => {
const headerPage = new HeaderPage()
it('Access home page', () => {
cy.visit(Cypress.env('url') + 'AutomationPractice/')
});
it('SSH to server', () => {
headerPage.sshToServer()
});
it('Verify content of Alert', () => {
cy.get('#alertbtn').click()
cy.on('window:alert', (alert) => {
expect(alert).to.equal('Hello , share this practice page and share your knowledge')
})
});
You can issue a new cy.wrap command with a value null and calling your async function in the .then function. Cypress will resolve that async function automatically and then move on to the next test.
First, you should convert your sshToServer method to an async(promise) function:
sshToServer() {
console.log('Connecting');
return new Promise((resolve) => {
setTimeout(() => {
console.log('Connected');
resolve();
}, 5000);
});
}
Then, in your spec:
it('SSH to server', { defaultCommandTimeout: 5000 }, () => {
cy.wrap(null).then(() => headerPage.sshToServer());
});
Note that I have also used a bigger for the spec defaultCommandTimeout since the default timeout is 4 seconds which is shorter than your sshToServer method and would cause the test to fail if not using a bigger timeout.
Sorry for late answer but i think this is the easiest way to do it
cy.wait(milliseconds)

Why is clearTimeout not clearing the timeout in this react component?

I am attempting to clear a former timeout before initiating a new timeout, because I want messages to display for 4 seconds and disappear UNLESS a new message pops up before the 4 seconds is up. The Problem: Old timeouts are clearing the current message, so clearTimeout() is not working in this component, in this scenario:
let t; // "t" for "timer"
const [message, updateMessage] = useState('This message is to appear for 4 seconds. Unless a new message replaces it.');
function clearLogger() {
clearTimeout(t);
t = setTimeout(() => {
console.log('wiping message');
updateMessage('');
}, 4000);
}
function initMessage(msg) {
updateMessage(msg);
clearLogger();
}
The funny thing is that this works:
function clearLogger() {
t = setTimeout(() => {
console.log('wiping message');
updateMessage('');
}, 4000);
clearTimeout(t);
}
...but obviously defeats the purpose, since it just immediately obliterates the timeout.
In practice, I should be able to trigger initMessage() every two seconds and never see, "wiping message' logged to the console.
The issue is that on every render the value of t is reset to null. Once you call updateMessage, it will trigger a re-render and will lose it's value. Any variables inside a functional react component get reset on every render (just like inside the render function of a class-based component). You need to save away the value of t using setState if you want to preserve the reference so you can call clearInterval.
However, another way to solve it is to promisify setTimeout. By making it a promise, you remove needing t because it won't resolve until setTimeout finishes. Once it's finished, you can updateMessage('') to reset message. This allows avoids the issue that you're having with your reference to t.
clearLogger = () => {
return new Promise(resolve => setTimeout(() => updateMessage(''), resolve), 5000));
};
const initMessage = async (msg) => {
updateMessage(msg);
await clearLogger();
}
I solved this with useEffect. You want to clear the timeout in the return function
const [message, updateMessage] = useState(msg);
useEffect(() => {
const t = setTimeout(() => {
console.log('wiping message');
updateMessage('');
}, 4000);
return () => {
clearTimeout(t)
}
}, [message])
function initMessage(msg) {
updateMessage(msg);
}
Try execute set timeout after clearTimeout() completes
clearTimeout(someVariable, function() {
t = setTimeout(() => {
console.log('wiping message');
updateMessage('');
}, 4000);
});
function clearTimeout(param, callback) {
//`enter code here`do stuff
}
Or you can use .then() as well.
clearTimeout(param).then(function(){
t = setTimeout(() => {
console.log('wiping message');
updateMessage('');
}, 4000);
});

socket.io and async events

I'm using socket.io and mongoose in my express server.
My socket is listening for events using the following code:
socket.on('do something', async () => {
try {
await doA();
doX();
await doB();
doY();
await doC();
} catch (error) {
console.log(error);
}
});
doA, doB and doC are async operations that writes on database using mongoose, but in general they can be any method returning a promise.
I want that 'do something' runs synchronously.
If the event queue processes more events at the same time I have consistency problems in my mongodb.
In other words if the server receives two 'do something' events, I want that the second event received is processed only when the first event is fully processed (after the await doC). Unfortunately the 'do something' callback is async.
How to handle this?
It's possible to implement a queue by adding the functions you want to run to an array, and then running them one by one. I've created an example below.
let queue = [];
let running = false;
const delay = (t, v) => {
return new Promise((resolve) => {
setTimeout(resolve.bind(null, "Returned value from Promise"), t)
});
}
const onSocketEvent = async () => {
console.log("Got event");
if (!running) {
console.log("Nothing in queue, fire right away");
return doStuff();
}
// There's something in the queue, so add it to it
console.log("Queuing item")
queue.push(doStuff);
}
const doStuff = async () => {
running = true;
const promiseResult = await delay(2000);
console.log(promiseResult);
if (queue.length > 0) {
console.log("There's more in the queue, run the next one now")
queue.shift()();
} else {
console.log("Queue empty!")
running = false;
}
}
onSocketEvent();
setTimeout(() => onSocketEvent(), 1000);
setTimeout(() => onSocketEvent(), 1500);
setTimeout(() => onSocketEvent(), 2000);
setTimeout(() => onSocketEvent(), 2500);
I would suggest adding a delay between each await. This will prevent deadlocks from occurring and fix your issue. For such things, I would suggest using the Caolan's async library.
Task delay example:
setTimeout(function() { your_function(); }, 5000); // 5 seconds
If your function has no parameters and no explicit receiver, you can call directly setTimeout(func, 5000)
Useful jQuery timers plugin

How to set a timeout for executed onClick function of react-router Link?

Suppose there is a react-router Link component.
class MyLink extends Component {
handleOnClick = () => {
doSomething();
// Expecting to have a timeout before routing to another route
};
render() {
return (
<StyledLink
to="/stock"
onClick={this.handleOnClick}
/>
);
}
}
How to set a timeout after onClick event before routing the history path?
EDIT
I have reposted another issue with a complete use case and solution.
It depends on what you want to do and what doSomething() does.
If your doSomething() function is not async:
handleOnClick = (e) => {
e.preventDefault(); //prevent transition
doSomething();
// redirect after 1 second
window.setTimeout(() => {
this.props.history.push(this.props.match.path);
}, 1000)
};
If doSomething() is async, then you can wait for it to finish and then redirect:
handleOnClick = async (e) => {
e.preventDefault(); //prevent transition
await doSomething(); // wait until it finishes
// redirect
this.props.history.push(this.props.match.path);
};
or combine both:
handleOnClick = async (e) => {
e.preventDefault(); //prevent transition
await doSomething(); // wait until it finishes
// redirect 1 second after doSomething() finishes.
window.setTimeout(() => {
this.props.history.push(this.props.match.path);
}, 1000)
};
Remember to handle any errors if the function is async.
this.props.match.path will get the path in the "to" property of Link.
Also, don't forget to use withRouter in your component.
Version:
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
Well you could preventing the link from navigating app but you gonna navigate it later programmatically afterwards:
handleOnClick = (e) => {
e.preventDefault(); //prevent transition
doSomething();
// and after timeout
window.setTimeout(() => {
this.props.history.push('/stock')
// history is available by design in this.props when using react-router
}, 3000) // 3000 means 3 seconds
};

Categories

Resources