Why we (generally) don’t recommend React Native (or other cross-platform solutions) anymore
TLDR: frequent interminable library conflicts, deep knowledge of native platforms required to debug issues, and devastating edge cases result in no real difference it total cost of development with a huge increase in complexity.
In 2015, Facebook’s cross platform framework (meaning it could run on both iOS and Android), React Native, burst onto the scene and took the mobile world by storm. In the next couple years, big companies like Airbnb, Soundcloud, and Discord hopped on board, as well as more niche but influential companies like Artsy. People everywhere were drawn to the many potential advantages: shared cross platform code, the availability of developers, development speed, and the promised performance parity with native code.
By 2018, however, some of those big companies had begun abandoning React Native, Airbnb being the most visible of them, writing a large, multi part post-mortem on React Native at Airbnb. That year was also the peak of the search term, which has been declining since then:
LithoByte also experimented with React Native, and developed our in house expertise to the extent that we even accepted clients for the platform. However, we quickly abandoned the platform; let’s look at the reasons why. First, though, a brief history of mobile cross platform frameworks.
The History of Cross Platform Solutions
We at LithoByte have always been suspicious of cross platform solutions since the first few came out. In 2012, Mark Zuckerberg famously said that betting on HTML5 for cross platform capabilities was the biggest mistake Facebook had thereto made as a company. The problem was many-fold, but not least was that performance was poor compared to native development.
Xamarin was and is another cross platform solution, but never got the sort of attention that either HTML5 or React Native did – perhaps because it uses a less known programming language, is made by Microsoft, or some other reason entirely.
Fundamentally, though, what all cross platform solutions share is that they will, by definition, always be at least one step behind native, in a couple respects. First, there’s always some extra layer that translates the cross platform code into single platform code – this will always cause some sort of performance hit; you can minimize it, you can try to work around it, you can design your app to make it look inconspicuous, but it will always be there. Second, when native platforms update, people who use a cross-platform solution can’t use those features immediately – they have to wait for the solution’s maintainers to incorporate the new features into the solution, or they have to write their own incorporation code, which they’ll eventually replace with the official incorporation (which means extra effort with little pay off).
This is the largest thing a cross platform solution has to overcome to be, in our opinion, worth considering for general application development. The hype around React Native indicated that, because it uses native components in the UI, it didn’t have the same sort of performance hit as other frameworks, and because Facebook was creating it, there would be little or no slow down as the native platforms updated, which led us to give it a try in spite of our reservations. Unfortunately, neither of these have proven the case.
Our experiences with React Native
1. The 80-20 effect
With React Native and many other cross platform solutions, a developer can very quickly build about 80% of their app using the cross platform framework. This experience is revelatory for many, because making so much progress so fast is a level of productivity that we rarely experience. “Gee,” we think, “if I got this much done so fast, surely the last 20% will be just as fast – I just have to fix these small bugs here or there and I’ll have a whole app done in a very short period of time!” This experience often is what developers will base their recommendations on – 80% is a long way, and the experience is so convincing, that they’ll recommend a switch based on that alone.
Unfortunately, that last 20% is a nightmarish slog. Fixing one bug causes another. Upgrading a package to include a bug fix causes a conflict with another package. Making it look nice on one platform breaks the UI on the other. You start having to write more and more platform specific code to fix these issues, along with ugly code to accommodate strange work arounds. The time saved by the first 80% is often dwarfed by the time lost to the last 20%, making the investment of dubious value.
2. More native experience necessary than native development
Our developers report that because of the obscure and arcane bugs they ran into with React Native, they counterintuitively used more of their native knowledge than if they were just developing a native app. Crashes output messages that were opaque in the extreme, requiring a deep, expert level knowledge of the native stack (on both platforms!) to diagnose, with subsequent sifting through libraries touching that part of the native stack to hunt down the cause.
This meant that LithoByte could not hire junior developers to work on React Native projects, and instead had to rely on our most experienced programmers, driving up costs for our clients in both the short and long term.
Also as a result of this, we did not receive any benefits from the shared architectural style with the web framework React – simply knowing React was not enough to meaningfully contribute to a React Native codebase because of the deep native knowledge base required to debug. As noted in Airbnb’s posts about it, rather than reducing the number of platforms from two to one, React Native increased the number of platforms to three.
3. Ecosystem volatility
As an app progresses, both business and technical considerations drive the addition of new features. To provide those new features, new libraries are often required. Unfortunately, libraries vary vastly in many respects, including: maintenance consistency, dependencies, version requirements, stability, and more. Because of this, every time a new library is added, it may have a non-trivial impact on other libraries, as you may need to upgrade, downgrade, or replace existing libraries (and the code that uses them) to support all necessary features.
This makes adding new features harder and harder as time goes on, and is part of what causes the 80-20 effect described above.
One interesting story from Airbnb relates to a bug wherein, for some devices, React Native would render the app as all white – meaning you couldn’t see anything, since everything was the same color. The inexplicable solution to this was to turn off a library included by default in React Native called Fresco. To this day, Airbnb developers don’t know why that fixed the issue. Think about that: Airbnb has some of the best engineers in the world, competing for talent and hiring from companies like Apple, Google, and the creators of React Native, Facebook. If they couldn’t figure it out, most other companies couldn’t either – let alone a startup just trying to save money.
We find that generally companies looking to save money are better served by concentrating on one native platform before building out a second later rather than relying on React Native or any other cross platform solution.
That said, there are times a cross platform app makes sense: cases when you are building a very standard application that doesn’t need off the beaten path features, a small scale app that doesn’t need to be high quality, and cases when you rapidly need to build out a first version that you don’t plan on maintaining long term.
For some startups we mentor, a cross platform framework can be a great way to get a proof of concept out the door – however, they will most likely, at some point, need to re-write it natively. That costs more in the long run, but if a proof of concept is needed to raise money at all, then a cross platform framework may help you get there.