Have you ever faced a long freezing frame in your simulator when re-rendering a list of elements ? If you are like me, the first solution that came up to your mind was : “Ok, let’s avoid as many re-renders as we can”.
But when talking about numerous re-renders in a React Native app, you should be aware that a few debugging steps and a bit of counter-intuitive thinking can lead you to a faster and more efficient solution.
Let’s set up our example situation : we have a Calendar, constituted of Day Cards, in which some Matches are displayed. Matches can for example be Football matches or Basketball matches. The user can select the displayed sport via a selector on top of the calendar.
However, you can see that the app is really laggy, it can take 2-3 seconds for the whole screen to update each time we select a sport… How would you deal with that ?
It is time to debug ! The first tool we are going to use is super efficient : ✨ React Dev Tools Profiler ✨. You can record your screen, while doing actions, and then see the result of your actions on the amount of time each React component takes to render. The flame graph indicates in colors the components that take the longer to render.
Here we see that Calendar content (the first line in orange) is the parent component taking all the time by rendering its children.
Let’s zoom into one of the calendar week :
And there we are : we see here that each DayCard (in orange) is taking a lot of time to render, compared to its children (in blue below). This means that the delay does not come from the children components used in the DayCard, but it comes from the calculations executed in the DayCard itself.
If you want more detailed information about how to use the React Dev Tools Profiler, and what improvements you may want to add to your code from it, go and check this article !
You would think that we got all the information we need right ? DayCards are taking too long to render, we need to optimize the calculations inside of them.
Okay so let’s dive in, our DayCard looks like this :
But the thing is… we still don’t know what’s taking time in here. Is it the computation of the background color ? Is it the computation of the matches to display ?
Time to introduce a new tool, the ✨ JavaScript Profiler ✨
Let’s open the Chrome DevTools menu by typing ‘J’ in Metro. Then with ‘Cmd + Shift + P’, search for ‘JavaScript Profiler’, and we can open the profiler tool. After having recorded a few actions in our app, it will show us all the javascript functions executed during the time of the record.
We see that the datePrototypeGetterHelper takes up to one third of the render time of our Calendar. Now we know what we need to improve : performance of date calculations. But first, where is it used ?
At first, the chart mode isn’t super clear. Yet we know what to look for : our DayCardToMemoize, the orange component in the React DevTools Profiler. And here is one, at the top of the red box.
Let’s zoom in a bit, to see what functions are executed during the render of our DayCard.
In the JavaScript Profiler graph each bar represents a JS function (let’s call it myFunction
) execution. And below this bar you can see other bars, that represent all the functions called by myFunction
.
For instance, on our picture, we see that one DayCard (the top line in blue) calls a useMatchesOfDay hook (second line in blue), which itself calls a Native function (in green), finally ending by calling hasSame (red) and fromISO (orange) functions several times.
So it seems that hasSame
and fromIso
functions are called a lot, and just these two functions are taking all the computation time. But why ?
Indeed, in the useMatchesOfDay hook, we can see that :
hasSame and fromIso are called for each match, and in each day card ! If they take a bit too long, no wonder the whole calendar renders will be slow : we easily call them 100 times for each render, with the same arguments as before !
What to do then ? We now know what is taking so long to render.
To solve this, let’s introduce ✨ Memoization ✨. Memoization is a programming technique that speeds up function calls by storing the results of expensive calls and returning the cached result when the same inputs occur again. Practically, we will use lodash.memoize that will do all the memoization.
Please note that we are not using React component memoization here, as React.memo
specifically memorizes a component's render output, preventing re-rendering of the component if its props have not changed.
Before diving into the implementation, it is worth noting that this solution is not perfect : the top notch solution here would be to rethink part of the calendar architecture to decrease the total number of date comparisons. But the memoization solution already gave great results and was really quick to implement.
So let’s go and memoize our function :
Let’s see what we did step by step :
And tadaaa :
Our app is now super fluid. Using one last time the React DevTools Profiler we can compare the time it took or the calendar to render before memoization implementation (which takes 800ms) :
And after memoization implementation (which takes 130ms) :
We can now filter our calendar 6 times faster !
I hope you found this article useful, and that it will allow you to speed up your apps a bit more !