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
- 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.
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:
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.
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.