Article summary
Managing timers in React Redux is often tricky. While trying to implement some features that required a lot of timeouts and time measurement of user interactions, we ended up using a simple approach for timers that I hadn’t seen before, so I thought I’d document it here.
Time in the Redux Store
We originally got the idea by thinking, “Wouldn’t it be nice if the time was just a value in the Redux store?” This would make it easy to do something like display a countdown timer:
const countdownTimer: ({expireTime, currentTime}) =>
(Launch at {`T-${expireTime - currentTime}`}! );
A simple, but somewhat CPU-intensive, way to do this would be to dispatch an action to update the currenTime
in the Redux store–say, with a requestAnimationFrame
. Instead, we found an approach that accomplishes effectively the same thing but with a lot fewer dispatches.
Only Update Time When Needed
Our approach was as follows:
- Store the
currentTime
in the Redux store. This is a globally shared value that any component needing the time can use. - When we want to “start a timer,” we record the time we want it to expire somewhere in the store (so
expireTime = currentTime + timeout
). - Then we do a
setTimeout()
that will dispatch an action to update the globalcurrentTime
with theDate.now()
time at theexpireTime
. - To know when a timer has expired, we just have to look at its
expireTime
and the global Redux store time.
The main advantage of tracking timeouts this way is that we don’t have to be extra-careful to cancel our setTimeout()
s if a the user moves to a different part of the app and our component gets unmounted. (This is a major cause of exceptions in React apps.)
It also prevents a lot of race conditions. As it doesn’t matter how many components are dispatching time update actions simultaneously, it’s easy to ensure currentTime only increases monotonically. Plus, it works great for countdown timers and plays nicely with time-travel debugging.
Hope this is helpful to you!