Move your development from Expo to React-Native the right way

Move your development from Expo to React-Native the right way

When developing with Expo, one of the challenges developers typically encounter is finding a compatible npm package. This is because the Expo package cannot add native modules - a huge obstacle for some projects. Yet, as there aren’t many pure-JS react-native packages around at the moment, the only way to continue your development would be to move the whole app back to a pure React-Native environment. Still, doing this can prove problematic if your code has already been built based on Expo specific features because you would have to remove a whole bunch of code and/or replace it with equivalent modules.

Luckily, Expo provides a way out of this sticky situation. A feature exists called Detach giving you what is essentially a simplified version of Expo client that you can tinker about with and build by yourself. Since you still have access to ExpoKit, everything else should still be running the same as before (with some exceptions, which we will discuss later).

To understand Expo detach, it’s worth revisiting the architecture of a React-Native app. React Native’s goal is to give you the ability to write your application in pure JS so that the application can run independently, yet separately from the mobile application. It can also be updated dynamically without recompiling the app. For that to happen, we need the native code (Objective C / Java) in place to act as “bridges” between the JS code (actual application logic) and the native features (camera, audio, sensors…). What Facebook did was nothing short of miraculous: they pre-assembled two mobile native clients with various native modules so that you could work with most common mobile features together with a Javascript engine to handle the actual business logic of the application. Additional native modules can be added into your React-Native app by either compatible npm packages or your own code. Expo went a step further by going ahead and packaging up two pre-compiled native clients (for iOS and Android) which you can’t update yourselves. The Expo detachment process gives you the pre-precompiled version of the Expo client, allowing you to add additional native features you need, and handling the control of the build process as well.

 If you’ve been enjoying the convenience of an Expo set up and you’ve never worked on a React-Native/mobile project before, the native client build process can seem terrifying. In particular because this approach can introduce a whole can of new bugs. Follow these simple steps for success:

 Disclaimer: the assumption is you are developing for both Android/iOS

 What do you need before detaching:

  • Exp-cli, which can be install using `npm install -g exp`

  • If you developed exclusively using Exp IDE before, then you may not have exp-cli installed.

  • A Macbook, or at least a computer with macOS. 

  • Latest Xcode version

  • Latest Android Studio installation with Build Tools 26.0.1

  • Normally, Gradle will always make sure that you have all SDK-related packages installed, and you may have to restart Android Studio a few times, but it may fail to install this specific Build Tools, hence manual method is preferred.

  • A properly populated app.json (

  • While you are at it, maybe it’s worthwhile preparing a proper icon and splash screen as well, so that the detaching process can help you bundle them with respective mobile clients. Otherwise, you need to do it manually later.

  • A backup push notification solution (if you’re using Expo Push Notification, as it doesn’t work with detached app)

  • Firebase Cloud Messaging / OneSignal can be a good candidate

Technically, you can detach the Expo app without Xcode/macOS but this approach is not recommended because it can render the detached iOS client crippled. It would be better to use a Mac, or at least one of the macOS Virtual Machines/Services available for the initial build.

 What happens now?

 Assuming you’ve prepared everything correctly, the whole detaching process should be quite seamless. After running `exp detach`, you will see two new folders named `ios` and `android` in your project root folder just like a normal React-Native application. By building the two native clients in those folders and deploying them to your phones, you should be set.

Warning: these 2 new folders should be committed and checked into your source control.

 How do I develop from now on?

  •  You will need to follow these steps so that an ExpoKit project can work properly:

  • Start the bundler using `exp start`

  • Build the native (ios/android) clients and deploy them to your phone.

  • Verify that your application is running as before.

  • Verify that your changes in JS code is served properly to your phone through the bundler.

The native clients on your phones will behave much like Exp client itself. What you need to be aware of is each time you add a new native module (described here:, you need to rebuild the native clients and deploy them to your phone again, otherwise your JS code will crash due to the absence of its native module counterpart.

 Please be advised that even though your app is now technically a React-Native app, it’s still using ExpoKit, which means you still need to use Expo Bundler, as opposed to React-Native bundler. And standard React-Native-cli commands like `react-native start-ios` won’t work.

 My push notification works but it opens to another instance of my app!

As we discussed  before, Expo push notification won’t work with your app. In fact, you need to remove anything related to Expo push notification in your code and server-side code to avoid your backend code “accidentally” breaking the end-customer’s client. Any push notification send to new client with old Expo push token will render the app useless.

After you’re done with purging your code base, consider implementing a third party push notification solution like OneSignal or Firebase Cloud Messaging through a wrapping library such as react-native-fcm or react-native-onesignal. None of these libraries has the simplicity of Expo push notification, but they get quite close.

There you have it, your Expo application, now with as many native modules as you can fit in. Veteran mobile app developers will rejoice, or at least, be comfortable with the transition. Frontend developers who have never done mobile development will be terrified. What once was a nice and tidy Javascript application is now exploded into a full fledge mobile application project. In the next blog in this series, I will show you how to move from Expo/ExpoKit to a pure React-Native application, just like the way it’s supposed to be.


I get this error message “ Too many classes in --main-dex-list, main dex capacity exceeded” while building app for debug, how to fix it?

It is a known issue. For some reasons, Expo client for Android supports API level 19 for debugging. One way around it (for now) is to remove the dev19 flavor and use API level 21 as your testing baseline. It doesn’t happen for the actual release build with API level 19, so you can rest assured that your app will still support Android 4.4 devices.

More work around:

 React/RCTEventEmitter.h file not found” or any of “file not found” message.

It is likely that the native modules you have added have dependencies on React, and was installed directly without using CocoaPod. There are a lot of issues when a non-Cocoa library has dependencies on Cocoa library, so the safest solution would be turning your non-Cocoa library to a CocoaPod one. Most React-Native native module packages should support CocoaPod.

 Keep an eye out for my next blog post, in which I’ll reveal the best way to move from Expo/ExpoKit to a pure React-Native application.

About The Author

Linh Le

Linh Le

Front End Team Leader

Linh Le is a front end veteran, with more than 10 years' experience under his belt. He mainly works with customers based in Nordic countries.