Over the past weeks, Sorare has been rewriting its React components to improve the performance of countdowns on its English auctions with Context API. Read about some of the performance challenges we encountered and overcame along the way.
If you’re interested in technical challenges like this, please check out our open engineering roles and apply for a position.
At Sorare, the global fantasy football game, we sell our digital trading Cards through English auctions. English auctions mean the highest bidder wins. It also means there’s an end date to the auction process. And since we sell hundreds of Card a day this way, we need to display a lot of countdown timers on our website.
How to display a countdown in HTML?
By definition of a countdown, it updates a lot. Our granularity is the second so we update our timers every second.
The first way that comes to mind is by using
You could make your gif and then use a JS library like libgif to jump to the right frame.
I think it might be a valid solution if we only showed the auctions when there’s little time left. But we show the auctions days in advance and this means the gif would need to have a huge amount a frames and hence would be super big.
Well, you can… And it’s pretty well detailed here.
But to quote the article:
just because you can do something with pure CSS doesn’t mean you should.
What about React?
sorare.com is a React TS application so things are a bit different. Even if you could in theory use plain JS with
refs but this is not super idiomatic.
For our countdown timer, it’s as simple as writing:
In case you’re interested, you can find the full source code of the test project on our public repository.
The way it works under the hood is with this timer called from
As we’ve seen, it’s not so great to have a lot of timers like that on your page so
react-moment, adds this
Instead of having one timer per Component, it is mutualized and updates all registered components every time it runs:
It’s better but it still has drawbacks. Calling
update directly on a Component is not super idiomatic either because it will render the component by itself and commit the changes to the browser. One reason why React is fast is because it uses a virtual DOM in which updates are quicker and it commits all the changes of a rendering at once so the browser has only to repaint once.
In the Profiler of the React browser extension it will look like this:
400 commits in a few seconds interval with 100 timers.
And in Chrome task manager I can see my tab sitting at a steady 15%+ CPU.
Moment.js is not optimized for tree shaking so in an effort to reduce the size of our initial bundle, the time came to try and move away from the Moment.js dependency.
Moving away from Moment.js meant also removing
react-moment and it’s a useful component. It also meant it was also the time to try and reduce the number of paintings in the DOM and thus the CPU level.
To keep a single timer,
react-moment uses a static property on the Moment class but there’s another way of providing data to your whole application: the Context API.
The idea here is to have a single timer, running in the Context provider, that provides a tick to every countdown component in the app and prompts them to update.
The ticker Context is as simple as:
(once again the whole test application is available on our public repository)
Writing the Timer Component is as simple as:
intervalToDuration is provided here by date-fns, the library we chose for date manipulation.
But in order to actually compare the performance of the Context vs the Pooled timer, we’ll use Moment.js to display the date by simply changing:
This time the performance extension output looks a bit more like what we can expect with only one commit per second.
The CPU consumption of the app has also been divided by 2.
With 1000 timers, the
react-moment version cannot keep up and timers are updated once every 3 seconds.
With our Ticker Context and 1000 timers, the app behaves just fine!
This Ticker Context is now in production on sorare.com! Our initial bundle is smaller, our User’s battery life is longer and developers are happier when they look at the React Performance extension.
In the blockchain world, “Don’t trust, verify” is the mantra. Remember that in software engineering, “Don’t trust, benchmark” is a must when optimizing.
Our team is currently exploring more engineering challenges such as building an infrastructure for a game to be played by millions of users, scaling and optimizing our transactions on Ethereum, and helping third-party developers to retrieve the information they need from our API to build side-games with Sorare Cards. Visit our open engineering roles here if you’re interested in the challenge!