The starting point: benchmarks needed

As anyone working with performances will tell you, prior to start improving performances you need to equip yourself with…🥁 something to measure them with 🎉

Within the react-native base project there is a benchmarking tool called PerfMonitor . From my understanding, this is basically the same standard React Perf tool — which you can read more about here. Basically it provides you with an analysis of how much time (in ms) your app is wasting in useless re-renders; it has one flaw though, which is that it can only run in debugger mode, which is not optimised.

In any case, it is still a quite valuable piece of code to use — and this is how I had it set up:

I placed these few lines inside the ComponentDidMount of one of our root components, called Logged — which is the one basically “owning” the main navigation of a logged user. With these lines uncommented, on “boot up”, this tool would start recording everything that happens and, after 20 seconds — which in my mind was a good “plateau” time to have everything properly loaded in the app — it would print out its analytics about rendering.

I can’t go into the details of our app architecture, but what I think you should know is that the mentioned Logged component contains the main navigation tab bar (composed of 4 tabs) and that the first main tab contains its own tab bar navigation, again with 4 tabs (one of which uses Airbnb’s Map component).

(and yeah, we use react-navigation)

The actual starting point

This was the baseline I worked with: before applying any sort of optimisation, this is what the Perf tool provided as benchmark.

Improvement #1: enforcing the no-bind rule

Being an app already in v2.x, with more than one year of development under its belt, no wonder the codebase was not polished in every corner — thankfully using Eslint we managed to find and update all files not adhering to its rules.

We obtained a good ~50ms per Component improvement in the top 10 (with only a couple of exceptions, like CellRenderer). Good, but nothing major — so I kept digging.

Improvement #2: changing comparator

Since some of our components have huge lists of items to show (like most apps), we need to react to props’ changes in specific ways. One of the checks we did in our componentWillReceiveProps was made on the list’s dataset, and when we changed the array comparison from the standard === to lodash _.isEqual() method, this is what happened:

This has been one of the most effective optimisation: apparently the lodash method is way better for comparing arrays — or maybe I’m just a noob and it was know by everyone except me 😇

Improvement #3: functional components FTW

Using this article I read a while back as a baseline, we decided to refactor a lot of component in order to make them functional. I was mind blown by the result:

Another huge performances improvement, by simple code refactoring! I was so excited: we dropped from ~750ms wasted on the heaviest component to a little less than 300ms!

But just as I was prepping to 🍾…

REALITY. STRUCK. BACK.

It won’t surprise anyone knowing that the app communicates constantly with a backend, which provides most data that needs to be shown throughout the whole app.

Turns out that, during all my tweaking and fixing and being excited about performance improvements… our backend was not fully working 👾

So, just when I was about to run the last benchmark, my boss pinged me over Slack to let me know that he fixed a bug on the backend, and that finally the pins in the Map were showing again.

Oh, no…

I run the benchmark.

My reaction #truefact

Having (again) the map full of pins created a whole the new level of wasted ms.

(small disclaimer: don’t consider this a rant towards those devs who created & maintain the Map component, ok!? Those huge numbers are surely caused by my inexperience)

So, basically, I was back to square 1… but I couldn’t stop now, I was so close to performance heaven!

Improvement #4: let’s focus on TabMap

Thanks to the tool, it was fairly easy to see that the tab containing the Map component was not optimized… at all. So I focused my brainpower into adding some small fixes to the tabMap component, plus moving some portions to functional:

We got some improvements, again. Not enough though… time for one last try 💪

Improvement #5: revising logic for TabMap

By changing the logic in the render() method for the tabMap, I managed to have the pins&markers loaded only when the component is actually “shown” — the code is something similar to:

This last improvement managed to get us “back to good performances”, considering that all these measurements are over a 20000 ms timespan: