React Native has been one of the groundbreaking players in the hybrid development game. Since its inception back in 2015, the technology has come a long way. My professional experience in developing mobile apps with React Native started two years ago and since then I’ve worked on a number of projects using the framework – from MVPs to legacy applications. While the former was just about utilizing rapid prototyping, the latter required the implementation of many functionalities – payment integrations, GPS fore-/background tracking, unit and e2e tests. Based on my experience with React Native and the multitude of articles and information about the technology’s redeeming qualities, this article investigates the technology’s shortcomings that both businesses and developers need to be aware of.
Key Advantages of React Native
Before diving deep into the challenges, let us look at the major pros for choosing React Native for your mobile project:
- New projects are exceptionally easy to get going. Setting up an empty project is fast, and live and hot reloading helps immensely (it is unified and made even better in version 0.61).
- The JS environment allows for more (web) people to get in and develop mobile apps. Also, since it is relying on React, pairing React Native with Redux (or Flux or any other preferred state management library) reduces the amount of code you need to make the app fluid.
- Besides unit tests with Jest you can even run e2e tests thanks to Detox.
- The component-style of development allows for more flexible user interfaces. A screen is called a component and each one renders itself as required. Due to the composition over inheritance mindset, flexibility is greatly enhanced.
- Direct communication with the individual widgets and items – just as data binding on Android but in only one file (the component).
- It has over 2000 contributors with tons of optimizations (even the new opt-in Hermes JS engine) and new cool features (hooks).
- It has over 86,000 stars on GitHub and several big companies rely on the technology making the community one of the most stable ones.
- React Natives position on the market is also fortified by an enormous number of 3rd party libraries and a ton of guides and tutorials created about it.
When I initially started working with React Native, I had the impression that it is too good to be true. The mobile technology does work exactly as advertised, but this does come at a price in the mid/long term. The cons of using React Native are not as clear from the get-go. That is why the following paragraphs are going to elaborate on why React Native requires more scrutiny when considering it as a possible route for your next project.
React Native Limitations
Understanding the limitations of React Native before you start developing your mobile app is extremely important. Read on to learn the challenges I have faced and how I dealt with them.
One way to fix that is to use TypeScript instead. However, if you’re working on a brownfield project, it will obviously take you a long time to migrate. Don’t take my word for it, check these guys’ conclusion here. Another option is to use Flow – it adds the static type checking instead of migrating to a different language. Setting it up properly (and thoroughly), though, can take some time too. For example, for an initial setup + 1st file took me almost a day. While there are ways of going about this, you need to figure out your strategy before the start of the project.
One of the greatest strengths of React Native is its community and abundance of libraries to choose from. However, this is also one of its weaknesses as most of these libraries’ maintenance is relatively low. Something that worked well yesterday might not work tomorrow and sometimes you need to use libraries that are not built for React Native (moment, lodash, commonmark). In my experience, not all dependencies support multiple React Native versions, which they should be considering the number of (hidden) breaking changes between the technology and the libraries (more on this below). Also, it is rare for a dependency to support the newest version and due to the volatility of React Native itself, it is a hard thing to strive for (although this is very slowly changing).
Use case 1
Let’s look into the following scenario that happened to me with a modal screen library. Once our team had to upgrade React Native to 0.59 due to Google Play requiring 64-bit APK files, and 0.59 was the only (newest stable) version that supported it. On the other hand, that required the team to refactor the component lifecycle methods (we were on 0.54 beforehand and 0.59 requires React 16.3 and above, which changed the lifecycle methods as it can be seen here). The library, however, did not work properly with the new lifecycle methods so I had to spend time refactoring the code to make it work like before.
Use case 2
RN 0.60 brings official AndroidX support. However, since not all libraries are up-to-date maintained, you will probably need to use another tool like Jetifier to migrate those. If you’re on pre-0.60 and have not migrated yet, you’ll need to use Jetifier with the reverse option to change newer, updated dependencies back to legacy support libs.
Use case 3
It turns out that there are only a couple commonmark React Native libraries. Sadly, none of them suited our use case as we wanted to have rich text formatting as part of the user’s input so the user can create and preview it. There were some examples of how to use commonmark.js as a base and build on top of it, but nothing final. I had to find an actual working example and build on top of it because our input had custom markers if the text had a common mark or not and some other small customization.
For the first use case I could have just stuck to using the UNSAFE_ legacy ones but I decided against it. Deprecating methods is done for a reason and using those just means delaying the inevitable. In the end, I spent roughly a week trying to reconfigure the project to run normally again.
As for the AndroidX and Jetifier use cases – the only advice I would give you is to rely more on Github rather than StackOverflow (Mike Hardy’s answers have helped me a lot). The problems I encounter are usually within React Native or the dependency in question.
A general conclusion and advice I can give here are to just allot more time (possibly do it frequently when necessary) for refactoring and optimizations. Once again, I want to stress on the frequency of those as it has couple benefits. Firstly – minimizing the time spent for this type of maintenance to a minimum, as well as refactoring small parts of the app compared to a couple at a time.
Slow bug fixing with limited framework testing
React Native itself hides bugs that may block your pipeline.
Use case 1
The issue I once came across was a blocker – it broke APK creation. It was up to users of React Native to come up with patch scripts to fix issues and the issues in question remained unfixed for weeks or months. This is not an isolated case, evidence being the frequent “fresh project cannot run” errors I’ve encountered regularly when looking for solutions myself. It seems Facebook regularly pushes not fully tested or unfinished versions of React Native and this is a great concern.
Use case 2
Scroll views’ property crashes only on Android. For iOS, it works perfectly but even the proposed solution did not work out for me and I had to resort to another one. The crash was reported way over half a year before the time of writing this. On top of all, this feature has worked with the previous React Native version just fine.
Regular updates of the React Native framework, coupled with thorough testing of the pipeline and the app itself. This might sometimes not be enough – if it is a relatively new bug, it might not have a fix, or worse – you are one of the first reporting it. This would mean you, as the developer, need to at least delve into the framework code and research what could be the issue.
Convoluted upgrade process
Since I have mentioned upgrading React Native, I have had my fair share of headaches with that, too. It’s not an easy task since it isn’t a straightforward process. My most recent frustration is related to the CLI being removed from the React Native package. The result was confusion as to how to even start upgrading. Also, there have been numerous breaking changes from one major version to another.
The upgrade CLI command supposedly works but rarely runs smoothly due to the changes it makes. After using it, a lot of files will change with most of them being React Native configured files like package.json or .babelrc. The problem, however, is that because you have overridden them by configuring the project for your use case, you will get a big mess of merge conflicts that you’ll have to read line by line. And since there are a lot of moving parts, you need to spend some time on a trial and error session with cleaning builds, packager (it caches a lot), dependencies, watcher, and everything in-between.
The chances are that the automatic solution will not help you, so there’s always a manual way (what I had success with) in the face of rn-diff-purge. It is just a diff between two versions of a clean React Native project – you need to upgrade everything by hand. The downside is that it takes even more time. Here is a piece of advice – if you have a CI pipeline, make sure it meets the requirements for the new React Native version, like the corresponding XCode version, before upgrading.
It seems that with React Native you are between a rock and a hard place. On one side, you want the latest version so you can still use your app safely in production, and on the other – you want stability instead of possible hidden bugs. This is concerning to me as a software consultant as I cannot recommend it to clients. I would rather use React Native for rapid prototyping project that the client needed to have been delivered yesterday, or a small project that will not grow significantly in several features. However, working on a production-ready app with requirements for long-term support poses questions whether it is a good idea or not.
React Native is a niche technology that needs to be used sparingly and with a bit of caution. The volatility of the dependencies and the API, as well as the unforeseen breaking changes, is something that nobody wants to deal with on a regular basis. Since all these are related to issues that are deeply rooted in the React Native environment, fixing them isn’t easy, to say the least. I think this harsh reality is based on the nature of the web development – volatility, frequency of change and hundreds of short-lived standards/libraries frequently emerging. In my opinion, the competition in the web world can tolerate this but when it comes to mobile – there’s always Google and Apple to compare and adhere to. As a result, freedom isn’t as warranted here, and the aforementioned giants require a more robust approach. From my experience with React Native and previously a little with Xamarin, hybrid development will always stay behind native. For better or for worse, there is not and probably will never be parity between them.
In conclusion, we can all agree that there is no perfect software. Still, between the very flexible component-based style of work and the fact that Facebook supports it, React Native certainly has a place in the mobile hybrid development. However, it is not the Holy Grail when it comes to mobile development. It presents a traditional tradeoff – one codebase which requires a lot of time for support vs multiple native (flexible) projects. It is a matter of choice, but as always, it better be an educated one.