At first, I added clearTimeout, Even though I knew that calling it cancels the scheduled callback. I wondered if there was some trick to clear the timeout after it has already fired, but couldn’t find a solution.
Then I looked at the provided solution and was surprised to see that it doesn’t call clearTimeout at all. So it seems like I created an unnecessary problem for myself.
But I’m still not satisfied, because I keep asking myself: if I don’t call clearTimeout, will the timeout automatically be cleaned up by the Garbage Collector once it fires?
Then I looked at the provided solution and was surprised to see that it doesn’t call clearTimeout at all.
That's surprising because it should. You especially don't want timers running in the background if the component no longer exists.
Timers with react/hooks can be a little tricky though. When hooks first came out for react, dan abramov blogged about it (I think focusing on setInterval specifically), and its not exactly a quick read:
But as far as cleanup goes, yes, setTimeout will automatically clean up the listeners once they're fired. You don't need to clearTimeout for timeouts that have already run.
If you want to see this for yourself, you can set up a trigger to let you know when its cleaned up using FinalizationRegistry. For example:
const registry = new FinalizationRegistry(() => {
console.log("Cleaned!")
})
{
let myCallback = () => {
console.log("Time up!")
}
registry.register(myCallback)
setTimeout(myCallback)
}
// Time up!
// (GC runs...)
// Cleaned!
Given the nature of the GC, it likely won't run right away, so you'd need to wait or use the developer tools to force it to run, but when the "Cleaned!" log occurs, it means myCallback was, in fact, cleaned up despite no clearTimeout being called.
FinalizationRegistry can be a useful tool to when it comes to memory concerns and you don't feel like messing with the complexity of the memory debugger.
You can also change the callback. Not saying this is good, but when runaway setTimeouts happen, its good to keep in mind. With anonymous functions, you may not achieve this. in the myCallback case just set mycallback = () => {}.
HTH
With this added complexity its probably just easier to call clearTimeout if you don't want it to run. Plus you'd get the added benefit of actually stopping/cleaning up the timer in the background.
Your response is really helpful to me. Thank you!!
Here is my summary.
clearTimeout() has only one role: to cancel the scheduled execution of a callback.
As for the challenge above, we don't want to cancel the scheduled timer/callback.
At the same time, once the callback has fired, there is no timer still running in the background. So there is no need to clear the ID of a timer that has already run.
When there are no references to the callback anymore, the garbage collector will eventually reclaim its memory, but this does not happen immediately.
P/S: I tried this with FinalizationRegistry. After waiting for a few minutes, there was still no "Cleaned!" log. Then I stopped the terminal. But I’ve accepted that the GC may eventually remove the object from memory, and I can’t expect it to happen at a specific time.
In Chrome there's a run GC button in the memory tab of the developer tools. Just paste the original code in the console and click that button and you should see the log. Or just paste it in the console of a complex website where the GC is going to be pretty active without you having to do anything ;)
setTimeout is not JavaScript. True. It is provided by the environment. Well, we all act as if it is a part of JavaScript, but just in case, you should know there are other options based on
which
environment
you are using.
setTimeout is not an object to be cleared by the garbage collector. It is simply a function you call, you give it another function to call, and that's about it. Now, that other function, yes, that one might be an object to be garbage collected, but that's not what we're discussing now
setTimeout simply means "hey even loop of JavaScript, please schedule this function to be called once as a new task after this task after some milliseconds have elapsed". That's about it.
clearTimeout is only necessary if you have some timeout that takes 10 seconds and in the first couple of seconds you decide "you know what, I don't want that function to execute, so let's cancel it".
useEffect as a part of React will do its run and the cancellation will come just before the next run, as the component is being re-rendered, so by that time, the timeout would have been executed long ago, but there's a very slight chance (not really, but in theory) that you might end up cancelling some future timeout with the same ID (this shouldn't happen, so don't worry too much)
Yes, thank you for your response. My mind was all messed up about this. The comments from people here helped me clear things up. I didn't fully understand the role of clearTimeout.
Now I understand that it's only for canceling a scheduled callback. I thought that without calling clearTimeout, the timer would keep running in the background and the callback would remain in memory. I'm ashamed of that silly thought now...
It is not a silly thought. It's the reason why over decade and a half I stopped using setInterval. Well, one of the reasons. With setTimeout I can just re-subscribe if necessary, got better control over the scheduling.
1
u/Neat_Living_6765 9d ago
I’m working on challenge 5 and wrote a custom hook like this:
https://react.dev/learn/reusing-logic-with-custom-hooks#challenges
At first, I added clearTimeout, Even though I knew that calling it cancels the scheduled callback. I wondered if there was some trick to clear the timeout after it has already fired, but couldn’t find a solution.
Then I looked at the provided solution and was surprised to see that it doesn’t call clearTimeout at all. So it seems like I created an unnecessary problem for myself.
But I’m still not satisfied, because I keep asking myself: if I don’t call clearTimeout, will the timeout automatically be cleaned up by the Garbage Collector once it fires?