Since my previous article, the landscape and tooling around React Native have changed, so I wanted to provide an updated post for React Native best practices in 2020. There are also quite a few more areas to cover, so this article is also intended to work as a continuation of the previous post.

Use Expo-Kit only if you know exactly what you are doing
This is the same thing I mentioned 1.5 year ago and it hasn’t changed yet in my opinion. Having worked with both React Native and Expo apps, read below why I have come to this conclusion.
Expo has evolved quite a lot over the past months but it’s still a very limiting framework overall. The problem is that limitation applies in many sectors which we will mention and this is why I still have to say that if you want to learn React Native and become a solid mobile app developer, Expo is not the way to go. It’s a big no-no towards that direction.
Don’t take me wrong; Expo has evolved hugely and it really tries to simplify things but the thing is, it can only provide simplification for whatever it supports. Period. And in order to support something, custom development is needed from the Expo team in order to keep this framework running. It’s a framework on top of the React Native framework and in order to be evolved it takes time and dedication.
I have built production apps with both React Native and Expo, so let me elaborate on what do I mean with the term “limitations” and why I still don’t favor Expo.
Expo Limitations
Native is limited by all means possible. And this is not a good thing, at least not yet. If you truly want to master mobile development, you need to learn how to use the basics of each platform like the gradle build system for Android and the Xcode for iOS. Expo tries to hide this complexity but doesn’t manage to do so successfully yet. The process is not so easy yet and it just makes it hard for everyone who is accustomed to the existing native process. Also, if you think you don’t need the native side just because you are coming from a web development background (just like myself), you are wrong!
Specific examples:
- Delays on when you get updates. The Expo team has to first develop and integrate RN updates for the Expo SDK to get the new updates and then release them. That takes up too many months :/
- You have to deal with an extra framework, which means on top of RN bugs, you ‘ll have to face Expo bugs as well. And as every bug, a fix might not be available soon.
- Cannot access the native side, including installation of 3rd party packages with native modules and also protection from native crashes. This functionality is not yet available in Expo although it is available in React Native iOS apps as the community provided a Pull Request.
- Restrictions on how you automate your app build process. In simple terms, you have to create an Expo account and there’s only one CI product that supports the automation of the Expo build process so far.
- You have to use extra tools like Expo client in order to install the app on your phone. And this in theory is OK for devs who don’t know the native side at all, but you end up with performance issues when testing on the device through the Expo client. The App is much slower than with the actual executable file.
The only cases I would favor Expo are the following:
- You have been building React Native apps for quite some time already, you know exactly what you are doing and you have concluded that for a specific new project and a set of reasons you have justified Expo is indeed a better choice.
- You want to check the world of React Native for the first and try some Proof of Concept (not a whole Minimum Viable Product though). There it makes sense to use Expo easiness of setup in order to start playing around faster.
Use React Navigation as your navigation library
Over the past 3+ years working with the RN framework, my fellow colleagues and I, have all come to the same conclusion:
No matter which navigation library we try, we keep coming back to React Navigation
Long story short about React Navigation:
- It’ s very easy to install as it’s a JavaScript library without native modules (Expo also includes it by default)
- It’s easy and rational to use, if you are a bit familiar with mobile navigation already
- It has started to achieve great performance and you no longer need to consider solutions that include native modules
- Has made quite a lot of progress over the past years — it is now in version 5.x — and is being maintained actively
If you want to give it a try, I suggest starting with the official docs. If you are new to mobile development, you might helpful my previous article, where I explain the different available types of mobile navigation and how they work, along with authentication workflow sample.
Handle & Log your errors properly
That’s a very important one. Nowadays, both React and external tooling have evolved a lot. That being said, we can now handle and also document our app errors so that we don’t have to suffer when a critical error occurs. Both points are very important here and will be analysed further below.
Include Error Boundaries to avoid UI crash
React library has provided a new tool to address UI crashes since React 16.3; it’s called Error Boundaries. As official documentation explains pretty well:
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.
An Error Boundary in a component that can be created from any React class component; we just need to follow a specific methodology.
In every React Native application you create, you should always have at least one main Error Boundary component at the app’s highest level. That way we ensure that all errors generated in any aspect of the app, will be “caught” inside this generic Error Boundary exception handling mechanism and the app will not crash or fail due to unexpected JS exceptions as shown below:
<AppErrorBoundary>
<AppNavigator />
</AppErrorBoundary>
and this is how it looks:



Now with this approach so far, we will be showing this generic error screen when things go wrong in terms of any JavaScript error within the app. It works but if the error is down at the bottom of the JS functionality tree, it means that the whole application becomes non-functional instead and users see this screen instead of a more specific screen on where the error is located.
In order to address that matter, we can create more Error Boundary components that are specific to the screen and show a similar UI (minus the missing information/data which we were unable to fetch and that’s why something went wrong) In example:



What we see above is a custom Error Boundary screen developed for the case that we cannot retrieve and show the image that should normally be visible where the yellow box lies. Instead of incapacitating the whole app with the use of image 2, we decided to create a custom Error Boundary screen and show it in plain black background. That way the user also understands that something is wrong with the image gallery not being able to load the preview of this specific image.
Of course, it makes no sense to create Error Boundary screens for all of the application screens necessarily, but it definitely does for screens that we identify as error prone along the way. The Error Boundary configuration will be shown in another post.
Protect your app from non UI JS crashes and hard native crashes
Your app needs to be protected against errors. An unhandled error means that your app will either not work properly in a partial or complete manner (JavaScript crash) or will become unstable and will close unexpectedly (native crash). The latter one is painful every single time. But we do get some tools to address it, like at least explain to the user what happened.
Handle JavaScript crashes all the way
The minimum way you should protect your app from JS crashes is with Error Boundaries mentioned above. But you can’t leave everything up to them. You need to provide try…catch statements wherever applicable. Standard situations include API calls, state manipulation, AsyncStorage invocation and pretty much everything that includes some promise invocation.
Handle native crashes
Native crashes are a true pain for all developers, but it doesn’t have to be this way anymore. My suggestion here is, to make use of a package like react-native-exception-handler which allows to capture native errors and show an informing message to the user before the app closes unexpectedly.



Now this is not perfect. It’s still a bit ugly and if you want to customize the UI modal/message you need to edit the native side of this package. But at least it’s something, which provides user feedback in a clear way. It’s way better than having an unexpected app close on startup which will lead to annoyed users performing bad reviews in the app stores. That way at least, the app looks more professionally handled in case of error and implies that the developer team has a way of tracking these things and will fix it as soon as possible.
Use an external tool for error logging
In order to address properly all the error categories mentioned we need to log them. The easier way to do that is to integrate an external tool that is built for this reason. There are a few alternatives out there like Bugsnag, Sentry, Rollbar etc. Choose the one you prefer and get started with them.
They will allow you easy error logging and error identification. They can usually show you the line that your code crashes with outside of the box integration. But this applies only to the JS side and only where the error occurred. If you want to know more info on like how the error appeared, in which screen the user was, what where the problematic data that were passed on, etc, you need to customize a bit further the functionality and make sure to provide the error to your tool inside of every try…catch
Make sure to use Hermes if applicable
React Native 0.60.4 has come with a new powerful tool to improve Android performance which is lacking in regard to iOS. It’s called Hermes and it’s bsaed on the JavaScript Run Engine. In Facebook’s words:
Hermes is an open-source JavaScript engine optimized for running React Native apps on Android. For many apps, enabling Hermes will result in improved start-up time, decreased memory usage, and smaller app size.
Unfortunately Expo doesn’t support it yet, so if you wanna try it, make sure you have the needed React Native version and check the docs on how to enable it as at this time Hermes is an opt-in feature and not enabled at default.
If you have used it already, we’d be very interested to see the performance gains it has brought to your app, so please go ahead and share your experience in the comments section below!
What do you think about this article? Offer your perspective and ideas in the comments section below.