is it necessary to clear timeout on button click in react? - javascript

can you please help me to tell is it necessary to clear timeout on button click in react ?
I have a example I want to show a alert on button click.here is my code
https://codesandbox.io/s/competent-torvalds-e84hq?file=/src/App.js
let id;
const onclick = () => {
// first way
id = setTimeout(() => {
alert("---");
}, 0);
// is it required ?
//clearTimeout(id);
// second way without clear
const second = setTimeout(() => {
alert("---");
}, 0);
};
useEffect(() => {
return () => {
console.log("-----", id);
// is it required ?
clearTimeout(id);
};
});
which way is better way ?
If I don't clear timeout on button click is there any performance issue ?.If there any memory leak if I don't clear timeout on button click

You only have to clear the timer if you don't want the timer callback to be called — if you want to cancel the timer callback. In fact, if you uncommented the code you've shown, the callback would be cancelled immediately and the alert would never happen.
If you don't want to cancel the callback, no, there's no need to clear the timer. The resources associated with it are released when the timer call is made; the browser automatically gets rid of its entry in the timer list.

Related

How to listen to an event and a timer at the same time in javascript

When the page is loaded, a timer with a random limit starts. I want to trigger some actions when the timer has run out and the user has pressed a button.
Update: Only when both two conditions are satisfied will the action start.
Update 2: To be clear, if 'button clicked' is event A, 'timer goes out' is event B, then the condition is A and B, not A or B, that's a little bit counter-intuitive.
(ps: release the click won't cancel event A)
const timeupEvent = new Event("timeup");
function f() {
document.dispatchEvent(timeupEvent);
}
setTimeout(f, limit);
button.addEventListener("click", (event) => {
document.addEventListener('timeup', action);
})
My code is shown as above. When the user clicks before the timer goes out, it runs smoothly, but when he clicks after that, the code can't run correctly.
I Believe that's because document.addEventListener('timeup', action) won't be triggered if the event is emitted before the execution of the code, but I don't have proper solutions for it.
Thanks for your help and advice!
Unless you actually need the timeup event you could simple check how much time has elapsed:
const pageLoadAt = Date.now();
const limit = 5000; // ms
button.addEventListener("click", (event) => {
if (Date.now() - pageLoadAt >= limit) {
action();
}
});

How to manage asynchronous state updates when using event handlers in render method?

Let me explain the goal of my code first. I have a react component called "Tile" containing a sub-component called "TileMenu" which shows up when I make a right click on my Tile, calling the function "openMenu". I wanted to have two ways of closing it:
clicking somewhere else
waiting some time
But, I also wanted it to stay in place if the mouse was over it. So I needed a function to cancel the timer, which I called "keepMenuOpened". If I moved my mouse away, openMenu() was called again to relaunch the timer.
Here is my code:
import TileMenu from './TileMenu'
function Tile() {
const [openedMenu, setOpenedMenu] = useState(false);
// state used to display —or not— the TileMenu component
const [timeoutID, setTimeoutID] = useState(null);
// state to store timeout ID and clear it
function openMenu() {
// Actually open TileMenu
setOpenedMenu(true);
// Prepare TileMenu closing
window.onclick = closeMenu;
// first case: click somewhere else
setTimeoutID(setTimeout(closeMenu, 3000));
// second case: time out
console.log('open', timeoutID);
}
function closeMenu() {
setOpenedMenu(false);
window.onclick = null;
console.log('close', timeoutID);
clearTimeout(timeoutID);
}
function keepMenuOpened() {
console.log('keep', timeoutID);
clearTimeout(timeoutID);
}
return(
<>
{openedMenu &&
<TileMenu
onMouseOver={keepMenuOpened} onMouseLeave={openMenu} // These two props are passed on to TileMenu component
/>}
<textarea
onContextMenu={openMenu}
>
</textarea>
</>
);
}
export default Tile
At first, it seemed to work perfectly. But I noticed that when I opened, then closed manually, and finally opened my TileMenu again, the delay it took to close a second time (this time alone) was calculated from the first time I opened it.
I used console.log() to see what was happening under the hood and it seemed to be caused by the asynchronous update of states in React (Indeed, at the first attempt, I get open null and close null in the console. When I move my mouse over the TileMenu and then leave it, I get for example open 53, then keep 89 and then open 89 !) If I understand well my specific case, React uses the previous state in openMenu and closeMenu but the current state in keepMenuOpened.
In fact, this is not my first attempt and before using a react state, "timeoutID" was a simple variable. But this time, it was inaccessible inside keepMenuOpened (it logged keep undefined in the console) even if declared in Tile() scope and accessible in openMenu and closeMenu. I think it's because closeMenu is called from openMenu. I found on the net it was called a closure but I didn't figure out exactly how it worked with React.
And now I haven't figured out how to solve my specific problem. I found that I could use useEffect() to access my updated states but it doesn't work in my case where I need to declare my functions inside Tile() to use them as event handlers. I wonder if my code is designed correctly.
The issue here is that you don't reset when opening the menu.
You probably shouldn't store the timer id in state, it seems unnecessary. You also don't clear any running timeouts when the component unmounts, which can sometimes cause issues if you later enqueue state updates or other side-effects assuming the component is still mounted.
It's also considered improper to directly mutate the window.click property, you should add and remove event listeners.
You can use an useEffect hooks to handle both the clearing of the timeout and removing the window click event listener in a cleanup function when the component unmounts.
function Tile() {
const [openedMenu, setOpenedMenu] = useState(false);
const timerIdRef = useRef();
useEffect(() => {
return () => {
window.removeEventListener('click', closeMenu);
clearTimeout(timerIdRef.current);
}
}, []);
function openMenu() {
setOpenedMenu(true);
window.addEventListener('click', closeMenu);
timerIdRef.current = setTimeout(closeMenu, 3000);
}
function closeMenu() {
setOpenedMenu(false);
window.removeEventListener('click', closeMenu);
clearTimeout(timerIdRef.current);
}
function keepMenuOpened() {
clearTimeout(timerIdRef.current);
}
return(
<>
{openedMenu && (
<TileMenu
onMouseOver={keepMenuOpened}
onMouseLeave={openMenu}
/>
)}
<textarea onContextMenu={openMenu} />
</>
);
}
You need to clear previous timer when openMenu called.
function openMenu() {
// clear previous timer before open
clearTimeout(timeoutID);
// Actually open TileMenu
setOpenedMenu(true);
// Prepare TileMenu closing
window.onclick = closeMenu;
// first case: click somewhere else
setTimeoutID(setTimeout(closeMenu, 3000));
// second case: time out
console.log('open', timeoutID);
}
function closeMenu() {
setOpenedMenu(false);
window.onclick = null;
console.log('close', timeoutID);
// timer callback has executed, can remove this line
clearTimeout(timeoutID);
}

Changing setTimeout not working in React with Redux

So I have a postActions.js in my code where I write all my Redux action codes.
One of the functions in it is a logout code.
This is the code.
export const logout = () => {
localStorage.removeItem('token');
localStorage.removeItem('expirationDate');
return {
type: AUTH_LOGOUT
}
}
This logout action creator is called after an expiration time has reached. This is done using a setTimeout function like below.
export const checkAuthTimeout = expirationTime => {
return dispatch => {
timeoutVar = setTimeout(() => {
dispatch(logout());
}, expirationTime*1000);
}
}
timeoutVar is declared in the top of the code, outside all the functions like this.
var timeoutVar;
Now here is my problem. Whenever a person is active in the app, I want to change setTimeout to a later time, so that he is not logged out when he is active. This is done with a code like this. logoutTime is the new time to be used for setTimeout.
clearTimeout(timeoutVar);
dispatch(checkAuthTimeout(logoutTime));
For some reason, the above code is not changing setTimeout. Lets say logoutTime is 10, it is supposed to change setTimeout to happen 10 more seconds from now, but it does not change anything.
If I only use clearTimeout(timeoutVar), the timer stops.
That is working. But when I also use dispatch(checkAuthTimeout(logoutTime)) the event happens at the old time itself. I am stuck with this for a long time. Please help.
I found the answer. Finally.
Since its React, my components were getting called multiple times and this triggered the setTimeout function multiple times. So there were multiple instances of setTimeout running. I had setTimeout called outside the function call like this -
clearTimeout(timeoutVar);
dispatch(checkAuthTimeout(logoutTime));
But checkAuthTimeout was also getting called in other locations without clearTimeout due to some lifecycle methods without clearTimeout and this created many instances of setTimeout. Safest option is to move clearTimeout inside the function. Like this.
export const checkAuthTimeout = expirationTime => {
return dispatch => {
clearTimeout(timeoutVar); // ADD THIS JUST BEFORE SETTIMEOUT
timeoutVar = setTimeout(() => {
dispatch(logout());
}, expirationTime*1000);
}
}
I just had to add a clearTimeout just before setTimeout all the time.

setTimeout, clearTimeout forming a reset timeout not working. console.log time issue?

I currently have a Modal popping up and want it to disappear if the user doesn't input their password for 30 seconds.
This works, but when I tried to implement it with an onChange event on the input then it stopped working. The weird part is, when I use console.log to test it, it works. Once I remove those console.log's, it doesn't work. Just curious if anyone has seen this before?
let TIMEOUT = null;
const setModalTimeout = randomFunction => {
if (TIMEOUT === null) {
// console.log('NOTE: set timeout before', TIMEOUT, new Date().toLocaleTimeString());
TIMEOUT = setTimeout(() => randomFunction(false), MODAL_TIMEOUT);
}
// console.log('NOTE: set timeout after', TIMEOUT, new Date().toLocaleTimeString());
};
const clearModalTimeout = (done) => {
if (TIMEOUT !== null) {
// console.log('NOTE: clear timeout before', TIMEOUT, new Date().toLocaleTimeString());
clearTimeout(TIMEOUT);
TIMEOUT = null;
}
// console.log('NOTE: clear timeout after', TIMEOUT, new Date().toLocaleTimeString());
done && done();
};
const resetModalTimeout = (randomFunction) => {
// console.log('NOTE: reset timeout', TIMEOUT, new Date().toLocaleTimeString());
clearModalTimeout();
setModalTimeout(randomFunction);
}
Above is the logic, below is the input tag.
<input type="password" placeholder="pin/password" id="password" oncreate={element => element.focus()} onchange={() => resetModalTimeout(randomFunction)} />
The main part of this issue is, if I uncomment the console.logs, it works perfectly. The way the code is shown now, it does not work. It will only go through the First cycle (so only work for 30 seconds even though I make a change to the input). and when I log, it clearly shows that change goes through. Any ideas? Of course, I don't want the console.log in the main code, also I am using hyperapp (existing code base).
When Logging, I can see the TIMEOUT value changing as expected.
I just realized what the issue was. With more debugging, my coworker and I realized it was related with the lifecycle hook. It should be oninput rather than onchange. On my computer, onchange was working just fine when logging (not too sure why if someone has an answer to that), but not on my coworker's computer. When changed to oninput it worked just fine for us both. onchange was waiting for the focus to leave the input.

react - how to force an element to redraw immediately

I have a toggle button in a react component:
toggleSpeak = () => {
this.setState({buttonOn: !this.state.buttonOn});
}
And the button changes its style depending on its state:
<img key="headphones" className={audioclass} src={this.state.buttonOn ? white : black} onClick={this.toggleSpeak}/>
This also triggers some stuff in a child component:
play={this.state.buttonOn}
This triggers some speechSynthesis playback, which sometimes takes a while. The problem is that I want the user to realize that something is happening right away. The button, however, doesn't change its style right away. As long as I'm triggering something else, whether it is through a passthrough property to the child as above, or through triggering a redux action, it still delays changing color for a few seconds.
I want to change color right away without delay, so the user knows not to keep repushing it. How can I accomplish this?
this.setState({}) function is indeed asynchronous so what you are claiming is likely to be true for a very short number of milliseconds considering that all you have in the trigger is
toggleSpeak = () => {
this.setState({buttonOn: !this.state.buttonOn});
}
The noticeable delay you speak of should be unnoticeable. I would think that the delay is being imposed from elsewhere. (say you require some other synchronous code to run before this.setState({}). Do show us more of the relevant code so that we can get better grasp of what's happening.
Are you doing the speechSynthesis in render?
You should call the function that does the speechSynthesis after toggling the button.
As far as UX is concerned, I would recommend that you show a loading indicator while you are doing a task that might take some time to finish. Also, you could disable the button until the speechSynthesis is finished.
toggleSpeak = () => {
if(!this.state.doingSpeechSynthesis) {
this.setState(
{buttonOn: !this.state.buttonOn, doingSpeechSynthesis: true},
() => speechSynthesis(args, this.setState{doingSpeechSynthesis: false}));
}
}
I'm not sure if this is the "react" way of doing things, but I came up with a solution that works. I split up the property I pass to turn on the player from the button toggle.
state = {
buttonOn: false,
play: false
}
Button attributes are the same as above, changing with the buttonOn state.
ChildComponent property:
... play={this.state.play}
Then, on the button toggle event I wait a half a sec before I change the play state. This is so the button will update it's style right away, and then all the player stuff can run after a tick.
togglePlay = (newValue) => {
this.setState({play: newValue});
}
toggleSpeak = (e) => {
let newValue = !this.state.buttonOn;
this.setState({buttonOn: newValue});
if (this.state.play != newValue) {
setTimeout(function() {
this.togglePlay(newValue);
}.bind(this), 500);
}
Then of course clear the timeout function on dismount:
componentWillUnmount() {
clearTimeout(this.togglePlay);
}

Categories

Resources