Sometimes, the best tool for the job is polling.
I recently found myself in a situation where I needed some code to run as soon as possible after an external third-party package has finished loading. Unfortunately, this library isn't distributed through NPM; it has to be added through a
In most cases, when you need to know when some asynchronous work is done, the best tool to use is a Promise. But that option isn't available to us in this situation.
The most widely-used example of a tool that does this is Google Analytics. When you copy/paste that “i,s,o,g,r,a,m” snippet,
window.ga will become a special function. I personally ran into this problem with an e-commerce tool, Paddle.js.
useRetryUntilResolved is a hook that will run a given function over and over until it returns a truthy value. Once a truthy value is returned, the hook wraps up and the loop ends.
This hook depends on the useInterval hook. Be sure to grab that one first!
Link to this headingUsage
Here's how you can use this hook:
useRetryUntilResolved takes a callback. We can perform whatever check we need, to see if “the event” has happened. In the case of waiting for a third-party library, you can check for modifications to the
window object. Really, though, you can use this hook for any sort of polling task.
If we return
false, the function will be retried. The default interval is 100ms, so it will try this function 10 times per second.
When we return a truthy value, we signal that the condition has been satisfied. The loop will be stopped, and a bit of internal React state will be toggled to
Link to this headingRunning code when ready
Most of the time, you won't just want to know if the third-party script has loaded. You'll also want to execute some code.
Here's how you can do it with this hook:
useEffect hook will fire whenever
isResolved is a boolean that initializes as
false, and only changes when our callback function returns a truthy value. It won't re-run this effect on every tick.
Link to this headingCustomizing the interval
useRetryUntilResolved will execute the provided callback every 100ms. You can specify a different value through a second argument:
You might wish to do this if your callback is computationally expensive, if you need to do a whole bunch of work to test if the condition is satisfied or not. Don't optimize prematurely, though! Check and see how long the code takes to run. If it's only a few milliseconds, I wouldn't worry about it.
You can also pick a quicker interval, if you really want the React app to update ASAP, but a worst-case delay of 100ms is acceptable to me. If something happens within 100ms or less, it feels nearly-instantaneous to humans.
Link to this headingThe big caveat
So, here's the thing about this hook: you shouldn't really use it in most situations.
There are usually lots of better ways to check when an asynchronous event has fired. You could use a Promise. You could use an event listener. If you felt fancy, you could use a generator!
This hook is meant as a last resort when you need to depend on third-party code that doesn't give you any other options.
When you do find yourself in this situation, though, this hook can be really handy. And even though polling isn't often the best solution to a problem, it's also not a bad solution. It feels a bit wasteful / inelegant, but honestly it provides a near-identical user experience, and at the end of the day, that's all that really matters.