Developing Mau King — React Native in Expo vs. Ejected mode and how to get both at the same time

This story is part of a series “Developing Mau King, the card game for Android

It is assumed you have the knowledge of basic concepts of React Native and an advanced understanding of Git.

  • What is Expo as opposed to the ejected mode in React Native?
  • How do you get to use Expo for development of your React Native project, and be ejected at the same time and use everything the ejected mode has to offer?

You will get answers to these questions by the end of this article.


The client side of Mau King has been written in React Native, and it turned out great. It has been published for Android already, and with React Native being a platform-independent SDK, in the future an iOS version should be seen.

As a front end developer with a regular job in React.JS, I was not required to do any real context switching to React Native — it is almost identical. The development is made especially easy thanks to Expo, but then it’s not omnipotent and you need to eject at some point,… but wait — let’s not hurry so much and clarify some basics before going further.

What is Expo

React Native has a “create-react-native-app” which builds an RN boilerplate. By default, it is built with “Expo”, which is a development environment that allows you to work with only JavaScript and isolates you from the actual native tech stack (Android, iOS / apk’s, Android Studio, XCode etc). You have to install and run the Expo client from the Play Store on your mobile phone. It connects to its server counterpart within your project on the development machine. Every time you make a change in the source code, the server compiles the code and pushes a reload request to its client on your mobile phone.

Ejected mode

The problem with Expo is that most 3rd party libraries cannot work with it, because they are designed to link directly to their native projects (Android, iOS or another). And these are not some marginal modules; they are 100% essential for most apps: react-native-fbsdk (for Facebook integration), react-native-billing (for Google payments), react-native-sound, react-native-keep-awake… Some of them have Expo-compatible alternatives, but it’s not the complete solution. In order to get full support for these essential modules, you have to abandon the Expo mode, by running the “eject” command provided in package.json of the project, which means:

  • Negative: No more live reloading through the Expo server-client connection. Instead, after each change you will have to run a build-script from the terminal first, and then build the native project and run it in the simulator from XCode or AS.
  • Positive: Directories with the Android and iOS projects are created, ready for loading in the Android Studio or XCode. Those 3rd party libraries become available for direct linking with the projects.

You need to eject anyway at some point, when you want to fine-tweak your platform-dependent projects and build binary releases.

Use both at the same time — is it possible?

— Spoiler: Yes, it is. Continue reading.

Once you decide to run the ‘eject’ script, it gives you a serious and frightening warning with exclamation marks like “We strongly recommend”, and “Ejecting is permanent!”, and so on. Basically, it tries to scare you away, and it really makes you think twice. Actual file system changes will take place, some files deleted and some new added, and with different files possibilities will change, too.

I spent much time postponing the eject, thinking how at some moment I will have to do it, mostly due to those 3rd party modules. So I kept mocking the libraries in order to keep the comfortable environment of Expo as long as possible.

But then is there a way to maintain two physical realities, two systems of file configurations at the same time? The fact that I tracked the development of my project with Git enabled me to do exactly that.

In shortest: Continue your development on the master and feature branches, and dedicate one branch for the ejected version. Every time you need to work with 3rd party libraries or something else on the ejected branch, rebase to your latest work on master.

Now let’s explain this in a form of a series of steps. You’re at the point where it says “Don’t do it! It’s permanent! We strongly recommend!” etc. Well, don’t do it. Press Ctrl-C and kill the script. Then follow these steps first:

1. Make sure you committed all your latest changes, your main development branch status is clean etc. I will assume your project is in $HOME/$PROJECT, and your main branch is master, for easier understanding.

2. Create a branch ejected at the HEAD of master:

$ cd $HOME/$PROJECT
$ git checkout master
$ git branch ejected

3. Clone your project to $HOME/$PROJECT-ejected:

$ cd $HOME
$ git clone $HOME/$PROJECT $PROJECT-ejected

4. Check out the ejected branch on the new clone:

$ cd $HOME/$PROJECT-ejected
$ git checkout ejected

5. Run the eject script on the ejected branch in the “ejected” clone:

$ cd $HOME/$PROJECT-ejected
$ yarn eject # or npm run eject
(...accept the "regular" option when prompted)

Now your primary repo and its clone look like this:

Note: The clone is only used during for the easier development, because you don’t want to re-install your node_modules and switch between branches every time you want to do something on the ejected branch. Also, since you are going to push back to origin any changes to the ejected branch that you make on the ejected clone, it is perfectly fine to just make backups of the primary repo.

All good, now what?

Now on your ejected clone (where the ejected branch is checked out), you can run your react-native link scripts, open the project in the Android Studio or XCode and add lines to MainApplication.java and so on… build the binaries, distribute them… and commit all these changes to the ejected branch, … and push them back to the origin, so that both clones contain identical ejected branches:

On the other hand, back in the master branch on the primary repo, where Expo still rules, you want to continue your regular development unrelated to the usage of those extra libraries. There is so much other work to do, right? But now, those two branches are noticed to diverge:

It wouldn’t be very good now, when you want to release a new version (or even have some more “ejected” work), that you do it on the obsolete ejected branch, because it has not picked up the latest that you did on the master branch. Ok, so when that occurs, we can sync them up in just a few commands:

# 1. sync master branch
$ cd $HOME/$PROJECT-ejected
$ git checkout master
$ git pull
# 2. rebase ejected to master
$ git checkout ejected
$ git rebase master # maybe you need to resolve some conflicts
$ git push -f origin ejected

Note: The push with force in the last step is necessary because after the rebase the normal push will not work. The push is necessary because you want your primary repo to contain the entire relevant state.

There you go! Your ejected branch now contains all the latest changes from the master development branch and you can make any new actions it:

Conclusion

Continue your development using both Expo and adding native libraries to the same project. Your project basically spreads onto two branches, instead of a single master branch.

This technique assumes a different purpose of a git branch: instead of the usual purpose for creating a different version or for feature development — it uses the extra branch as extension of the project in the different development mode. Hence “at the same time”, because you develop in both modes interchangeably.

Any questions or feedback, please use the comment section.