Using React Native to build professional consumer apps

At Ueno, we built our own starter kit for React Native. Why did we do it? How did we do it? Read on.

React Native has grown massively since it was open-sourced by Facebook back in 2015. Today, it’s used by serious companies like Walmart, Bloomberg and Tesla, developers have access to more than two thousand libraries and tools for it, and, most importantly, there’s a very large community to back it up.

At Ueno, we started fiddling around with React Native in early 2016. We’d had a good experience with React, and Native looked really promising and easy to develop for. But at the time, we found that it was not quite there yet in terms of stability. We didn’t really think it was in good enough shape for us to use it to build professional consumer apps.

About year later, Facebook and Expo released create-react-native-app, a starter kit to make crafting apps easy and hassle-free. So we took another look, and found that while it would be great for beginners and entry-level developers who need to get things quickly up and running for prototyping, the starter kit still had too many drawbacks for professional use — it’s too slow, it doesn’t offer native navigation, you can’t control the build system or install third party native dependencies, you can’t modify existing native code, and continuous integration and delivery isn’t what you’d hope it to be.

We liked the idea of a starter kit, but clearly create-react-native-app wasn’t for us. So we decided to make our own.

The beginning of Ueno’s React starter kit

As we set out to assemble our own starter kit, it was important to us that our it be well documented, with carefully selected opinionated dependencies, and it had to be automation-ready.

For great apps, navigation is key. We choose react-native-navigation because it has a large community, it’s in active development, and it’s easy to contribute to. All screens are created on the native level, with their own React context, which gives it way better performance than react-navigation, the solution that create-react-native-app uses.

To deliver updates of assets and runtime code we use code-push. That way, every commit to the repository results in updates over-the-air to test devices within 30 minutes or so. Everything happens automatically, and for soft launches we can even control how many users get the update. To avoid conflicts, the library takes care of matching native app version with the built JS bundle.

Managing environment variables with react-native-config is necessary to control the environment for different build states. We keep secret environment variables within Travis so they are not exposed to the outside world, even in pull-requests.

In the documentation, you can find a list of all the features.

Modifying libraries

We use community driven libraries and tools extensively in the Ueno starter kit. But sometimes we need to go off the norm and modify a library so it fits our needs better.

We use this great solution where you can automatically patch node_modules files after installing so we just include patch files with the repository and everybody will get the same patched codebase after install. Currently we are mostly patching libraries that have a missing Podspec file.

If we think other people can use our modifications, we open a pull-request to the source library and try to get it accepted so we don’t have to keep a local copy. One example where we did this was with 3D Touch for react-native-navigation.

Testing and linting

We use eslint to ensure quality code standard, and run series of tests on every commit in every branch on a CI. We even have our very own ruleset (a superset of Airbnb’s config).

Unit testing is something we want to do more of. We always have at least basic ones that test that the whole app renders.

We use jest to write the tests and enzyme as a test runner.

Sample jest snapshot and enzyme test

Integration testing (E2E) we do with a tool called detox. Created by Wix, it works by running the app in simulator where it does whatever we want it to do by writing code that scrolls, swipes, presses buttons, etc. We then write comparison tests with jest or mocha.

Testing is hard but it makes delivering continuously so much easier. And we do love to automate all things.

Continuous delivery

Flowchart of the pipeline

Imagine merging a pull-request on GitHub, and 30 minutes later your QA team being able to test the feature on their device and give you an report.

This is where testing is crucial because you wouldn’t want to automatically deploy a broken build and waste time in reporting, investigating and rollbacking.

There are a lot of moving parts involved in Continuous Delivery. We have been using Travis for a long time along with GitHub and it has worked well for us. Only thing we can complain about is that build times are slow on the OSX images (compared to Bitrise which we also use occasionally).

Commits on master are automatically built on Travis/Bitrise.

are automatically built on Travis/Bitrise. Code-push delivers JavaScript and other assets over-the-air to Staging

Detect native code changes in ios or android folder (or forced build via commit message) and if so, build native iOS and Android releases and upload to iTunes Connect and Google Play Store.

or folder (or forced build via commit message) and if so, build native iOS and Android releases and upload to iTunes Connect and Google Play Store. Staging changes can be tested on iOS in TestFlight or Android Beta channel.

changes can be tested on iOS in TestFlight or Android Beta channel. Manual promotion of Staging to Production when green light is given for release.

Analytics and user engagement

We don’t want to release an app without being able to measure its usage, growth and traction.

We like using Firebase as an all-in-one solution for app analytics, user engagement and growth. Most of the data can be shared between their products which makes it a nice one-stop shop.

Authentication (Anonymous/OAuth/Credentials)

Cloud Messages (Push Notifications)

Invitations (SMS, E-Mail and more)

Realtime Database (JSON)

Storage and Web hosting

Analytics (Screens, installs, audience targeting and more)

Dynamic Links (Deep linking)

Crash Reporting

Performance Monitoring

Adwords and Admob (Display ads in your app)

Error reporting

In our experience, bugs happen more often in react-native projects because of the extremely fast release cycle, so catching them and fixing is crucial.

We have used Sentry, Firebase, Bugsnag and Crashlytics with good results in the past but we tend to like Sentry the best because it has very good react-native and code-push integration that shows mixed stack traces (native and JS).

(By the way, Crashlytics has now become a part of Firebase. Also, Bugsnag recently announced great code-push relationship, so we may give it another go in the future.)