Sequential Interval React Hook

Create a React hook that can simulate a pulse-like cycle between animation states through different durations per animation state.


Here's the hook in an example.


We have an animation heartbeat player that switches between four animation states, each with its own user-configurable duration.

We have a ping service that sends out a ping to our component on an interval, or heartbeat, and each ping kicks off an animation cycle. This cycle goes through the sequence: rest -> tick -> exit -> enter -> rest.

The original implementation was built into a class component using a local MobX observable and nested setTimeouts and has worked very well for the past few years.

This is a rough idea of how the nested setTimeouts are set up inside of the ping.

We're at a point where we need to update the renderer housing this logic and I thought I would attempt to do it with a functional component.


The four animation states we need to switch between are tick, exit, enter, and rest; while each of our widgets has CSS animations that are tied to a className of status_[animationState].

Each of these animation states needs its own duration that is user-configurable.


First, I tried to implement something similar to what we see above in a useEffect and setState. The downside here is that the useEffect is new every render so I wasn't able to track timeouts effectively.

The second thing I tried, was to leverage the useInterval hook that Dan Abramov created. The downside here is that the callback is a ref so it never changes, which means I can't pass it a different callback for each step/duration.

Finally, I settled on a mix of refs for persistence and a custom hook to handle the setTimeouts.


I thought I would be able to jam the setTimeouts in an array and use a for of loop to run them, one by one. This ended up running them "out of order."

I ended up coming across two solutions that helped me piece it together, How to resolve a useReducer's dispatch function inside a promise in ReactJS and Why Using reduce() to Sequentially Resolve Promises Works.

The idea here is that each is wrapped in a Promise and added to an array where we can loop over them in a reduce, awaiting the previous Promise before starting the next.

This worked like a charm!

Creating the useStepInterval Hook

The custom hook is where the magic lies. We start with two useStates, one for the animation state and another to determine if the animation cycle is running. We'll return the status and the ability to set isRunning so our component can turn it on/off.

Next, we set up a useEffect that will watch isRunning to see if the cycle should start. In this hook, we'll have two functions, one that sets up the Promises and another that will run the reduce over the Promises.

Using the useStepInterval Hook

In our component, we can now run our hook and have a ref that catches the ping from our player service, which sets isRunning to true, starting the animation cycle.

Now we have an animation cycler that can be started from our component, and the best part is, we can have our component be functional 😀.

I definitely learned more about refs and how to work with promises during this feature. Hopefully, there will some more refactors to a functional component that can challenge other areas.

Discuss this article on Twitter