React Native is a JavaScript framework that allows us to build mobile applications. It is based on React, Facebook’s JavaScript library for building web user interfaces, but instead of targeting browsers, it generates fully-native mobile applications. This means that you can use JavaScript and a single codebase for creating apps for Android and iOS, without using two different frameworks and programming languages. Different to other frameworks, React Native also “converts” the JavaScript into native code (i.e. Objective-C or Java), without leaning on WebViews.
Presented this way, React Native seems like the perfect solution for creating cross-platform mobile applications. However, there are some not very straightforward issues that need to be taken into account during the development of React Native apps. This was the main topic addressed by Ofir Dagan, R&D Manager at Wix, during his talk at Codemotion Rome 2019. In this article we will provide a summary of his talk, based on three years’ experience in developing mobile apps with React Native, within a team of around 60 developers.
Why React Native is so problematic
It should be clear that the main advantage of using React Native is to build multiple applications (for multiple mobile OSs) with a single codebase. However, this has implications that unfortunately affect performance. As highlighted by Dagan during his talk, writing the React Native app is not easy and this is due to the following reasons:
- React Native adds an abstraction layer over native APIs
- Since it uses JavaScript, all the applications are single-threaded
- It is often more difficult to accomplish things that otherwise are very easy in regular native apps (managing lost lists efficiently is one of these cases)
- Building apps is a more complex task
In other words, while React Native allows us to use a single codebase for building more apps, it does not solve everything. If you want a truly performant app, you need to write it natively.
Solving problems in JavaScript
While Dagar often underlined that native apps are probably the most performant ones, he also explained that exploiting React Native still remains a valid alternative. And in his experience, 90% of the problems can be solved in JavaScript by just looking at the code, identifying problematic snippets and applying some proper optimisations to them.
In the following, we will summarise some of the examples provided by Dagan during Codemotion, all of which are real cases analysed (and solved) during the development of a real, popular, complex React Native app on production.
A slow send button
The first case study proposed by Dagan was a simple send button embedded inside a chat module. Such a button has to be disabled when the text input is empty and becomes enabled when user starts writing.
The problem they noted in this case was that the button became enabled some seconds after the user started typing. This is one of those cases where finding out why a weird behaviour is happening might drive a developer completely out of target. One could think that this is something due to React Native and the additional abstraction layer that was causing a performance issue. Instead, what Dagan and his team discovered is that they were rendering the entire list of chat rooms at every change in any of the other chat rooms. So in this case, changing the text and enabling the button caused a lot of useless data loading and rendering.
While this may look like a shoddy way of building such a simple feature, it actually was caused by the need of programming in a functional manner. Dagan explained that they solved this issue by saving the unchanged chat rooms using memoization.
Delays in delivering messages
Another interesting example discussed by Dagan involved a messaging module. In this case, the message sent by one were received with annoying delays by the recipient. This video depicts the issue:
https://www.youtube.com/watch?v=gCmSf67won4
The problem here was componentWillReceiveProps being called too many times, which in turn triggered every new render cycle, due to a call to setState. While this is of course related to React Native, knowing how it works under the hood can help in solving such kind of problems by just optimising the JavaScript source code. After discovering this issue, the result was much more performant:
https://www.youtube.com/watch?v=d6fJaLqBlvY
Animations
Another interesting case study involved the implementation of a custom animation. While designers can have interesting and very creative ideas, implementing is often much more difficult than describing.
Dagan and his team first tried a React Native approach based on the library Animated which, however, did not work. After some trials, they opted for a native implementation of such animation, which worked perfectly during the tests, but did not result in a valid solution when integrated in the application. Finally, they had to rewrite all the animation based on React Native’s LayoutAnimations, which in the end resulted in a working solution.
Module Load Time
The last example discussed by Dagan is about importing modules. The application developed by his team is indeed divided into many independent modules. They discovered that delaying the import statements by requiring each of them only when needed made the application much more reactive and efficient. So the solution consisted in removing the import statements from the top of the file and instead requiring them only when needed.
Of course, they figured out this after some tests, but this is just another example that shows how just working on JavaScript allows to improve the efficiency, without the need of going native.
Conclusion
Dagan concluded his talk by highlighting that React Native is not to blame in most cases. Instead, this is just how JavaScript and React work. To prove this point, he also released a project (available on GitHub) that includes a simple application implemented in two versions: one that is very laggy, and another one that incorporates proper optimisations, resulting in a much more efficient solution.
Finally, Dagan suggested some guidelines that any developers (especially React developers) should always take into consideration:
- Know your crucial app flows and keep monitoring them: it is important to monitor the app flow, in order to be notified in case of performance degradation
- Avoid writing anonymous functions as props, since they could potentially have a huge impact
- Know your components and do not think that writing pure components will solve everything (see the above example about animations)
Know your components life-cycle methods: each component is different and might use different life-cycle methods.