My Journey from ReactJS to React-Native

Mega-post with everything I learned + lots of code examples

As a ReactJS developer that has dabbled in ReactVR, I was excited build my first React-Native app.

I’ve been developing a cross platform React-Native app that supports a kid’s toy, a smart ball that counts it’s bounces.

The ball starts at 0 and kids compete to get their ball to a million. The app tracks scores, has fun animations, and provides trophies to further gamify the oldest game known to mankind.

With the App releasing soon, this is a mega post about the things I’ve learned about React-Native over the past 4 months as lead developer.

Having never worked with React-Native before, I smacked into wall after wall until I finally got a solid footing on how React-Native was different from ReactJS. Beyond that, a lot of the design patterns are just plain React.

Below I’ve curated a list of things I had to figure out this year in order to jump from ReactJS to React-Native as a developer.

To those at the beginning, I hope this can help you along your path to mobile development.

Apps don’t have a browser window or DOM

This may seem obvious, but if you hadn’t thought about it before it greatly changes which npm/yarn components you’ll be able use in your app. In React-Native there is no DOM. Therefore, any component designed in such a way that requires a DOM will not work at all in React-Native.

This means that when you’re searching for a doohicky that does something, make sure you are googling react-native-DOOHICKY and not just react-DOOHICKY.

Save yourself some pain.

Apps don’t have exposed routes

This is another item that may seem obvious. Even if you’re retrieving data from a Rails or Node.js backend, you’re not building something with publicly accessible web routes.

There is no DOM, no Browser, and thus there is no address bar to use to navigate around. In a lot of ways this makes things easier, you can use a variety of different packages to handle routing, but I chose to use react-native-router-flux

In my project I created a file called AppRouter.js that I render after my Redux store in Root.js. Inside my app router I simply import like so:

import { Scene, Router } from "react-native-router-flux";

Here’s an example of how I used react-native-router-flux:

You can see that I wrap a Router component around a series of scene components. The key inside the Scene is what I’ll use later to trigger a route.

Here’s an example:

Actions.main({ type: "reset" });

You can see that above I have a scene with the key of “main.” All I have to do to trigger the component listed in that scene is call Actions.main().

The type “reset” refers to the type of transition that should happen when resetting the state.

Higher Order Components are Awesome

You’ll notice that in the router example above I’m wrapping the components in another component. This is called a higher order component. This allows me to reuse a great deal of code and as you can see above it’s really easy to use.

By extending WrappedComponent, the higher order component has access to the incoming component's props.

I created another HOC that would detect whether the app was connected to the web by setting an event listener that checks for connectivity.

If the app loses connectivity it triggers a pop up modal that won’t go away until connectivity resumes.

Sometimes You’ll Need to Write Java and Objective-C

In this project my client wanted to use the headphone jack like a modem to send data from the ball to the phone. ReactNative can do a lot, but at the moment of writing this it can’t access the headphone jack.

I started out with these prototypes to simulate the smart ball.

Except for this one element, ReactNative was a great choice for building this project.

In ReactJS create-react-app is becoming quite the standard with Dan Abramov voicing strong support for the way it's simplifying the intensely layered process of stacking a ReactApp. There is a create-react-native-app which is also great, unfortunately though, if you need to do ANYTHING with the native code you need to eject.

When you eject instead of all the Android, IOS, Android Studio and XCode project files being hidden, they become exposed and can be edited in the same way an IOS or Android developer would work on the app. There are a few differences however.

React-Native should be handling the views. IOS loads appdelegate.m, written in objective-c and then launches react-native's views after loading itself.

You can load an IOS view as a separate page, but that’s a special case scenario I didn’t need to do.

Creating A Bridge From IOS/Android to React-Native

If you need to do something with native like receiving headphone data to be decoded into a count, you’re going to need to send that data to React-Native.

If the data needs to continually update you’ll need to setup an event. If you only need the data on load you can pass constant values through as well.

IOS to React-Native Bridge Example

My co-worker Salil figured out how to do this on IOS (with not much documentation).

Here’s an example of some of the objective-c used to build a custom bridge from native IOS into React-Native:

This was a huge pain in the ass to say the least so take this into consideration when you’re planning your project.

This bridging needs to be done in both IOS and Android (Java), so this obviously reduces the overall time saving benefit of a shared codebase.

Android Bridge Example

Here’s essentially the same thing written in Java. My co-worker Justin successfully got the bridge working on Android. He also found it to be difficult due to lack of documentation and working examples.

More Thoughts On Bridges

Using Objective-C, C, Swyft, or Java to create your own bridge is pretty advanced if you're a typical React developer without a background in mobile.

Avoid ejecting for complex stuff like that if at all possible. I didn't have a choice. If you want to install anything that requires interfacing with the native layer you're going to have to interface in those native languages.

A great example is react-native-firebase which requires a lot of native setup, but allows you to multi-thread Firebase methods vs. running Firebase on just the Javascript thread.

Other packages that require adding libraries natively like react-native-device-info can be used to get data about the user’s device.

A package like react-native-vector-icons provides customizable Icons for React Native with support for NavBar/TabBar/ToolbarAndroid, image source and full styling.

Styling IOS vs. Android

To start you don’t really use separate stylesheet files, the idea is that you’re going to be breaking up all your code into tiny pieces, where everything about that piece is located in the same file, the component.

If you’ve factored your code properly this structure really simplifies styling as it’s no longer a wild goose chase to find the specific property you need to adjust or where to adjust it.

import { StyleSheet } from "react-native";

You can pass a StyleSheet or plain object filled with camelCased CSS styles into the style={styles.background} prop on most components and should expect to use inline styles from now on.

StyleSheet.create({}) loads your styles into memory at the moment your app launches. StyleSheet objects usually don't work if you need to set dynamic values in the style prop like for an animation. In this case you can use a plain Javascript object to build your JSS, but it's preferable to use StyleSheet from react-native.

I really like JSS because as you see above I'm able to utilize Javascript methods right in the StyleSheet.

Incredibly powerful!

For some reason IOS and Android don't always respond the same way to the JSS. In IOS there is a preference for setting position using top and using negative numbers when necessary to position elements on the screen. Android for some reason doesn't like negative numbers, so you'll need to use bottom instead.

But wait, aren’t we deviating from the single codebase rule if we need a separate stylesheet for Android? Not exactly. Back to those ternaries...

If you import Platform:

import { Platform } from "react-native"

React-Native will give you the platform of the device it’s running on. Therefore you can make statements like this in your JSS:

react-native-cross-platform-responsive-dimensions

I had an idea about how I might cut the code down in my JSS. I started writing a series of methods to help me position things cross platform without needing so much extra code in the StyleSheet:

react-native-cross-platform-responsive-dimensions provides an API of methods to create values based on device dimensions, as well as methods that allow you to target specific operating systems, types of devices, and even specific devices all within your JSS stylesheet.

npm install --save react-native-cross-platform-responsive-dimensions

It includes methods like:

crossResponsiveHeight(IOS-phone, IOS-tablet, And-Phone, And-Tablet)

Using this I’m able to set each device using a percentage of the device dimensions independently of one another, so that when necessary I can tweak that element to work without breaking something that works in IOS or on a phone.

Support for Hot-Reloading on Multiple Devices

The ability to hot reload multiple devices at the same time became available around the time I began writing and using my component for styling.

It occurred to me that with such fine control over every operating system and device type, I could save time by previewing on as many devices as I could plug into my computer, styling everything simultaneously.

Custom Fonts

Installing custom fonts were difficult at first. I haven’t researched how to do this without ejecting out of create-react-native-app, ejecting is the same as initializing a new react-native project with react-native init. This exposes all the native code wheras it is hidden within a create-react-native-app project.

There are a few things to know. First off, IOS and Android look for the same font, but expect them to be named different things.

This means that if you call your custom font in your stylesheet, unless you do the following, the custom font will only ever work on one platform or the other.

I handle this with the crossPlatformOS(ios, android) method that's part of my react-native-cross-platform-dimensions component.

You’ll also need to copy the fonts to a folder in your project, as well as make sure they are linked to the the react-native project. Make sure you restart your server and recompile whenever you add a custom font.

Here’s a guide on setting up fonts => React Native Custom Fonts

ReactNative Elements

Making a touchable button is a little bit more complex on mobile than it is on the web. On the web you make a button and an onClick event handler and that’s it, but on mobile you have to specify things related to touch and there are some additional options to choose.

Libraries like React-Native Elements strive to make this much easier by constructing useful design elements into easy to use components. That said, when these things fail me for some reason I always fall back to the long way and use the ReactNative commands instead.

Nader Dabbit, the developer behind React Native Elements, has a great podcast called React-Native Radio.

I’ve been listening for a while now. He brings a really great range of guests from the React-Native world to talk about what they’re doing.

When To Use Redux

In my personal opinion any application more complex than a brochure should use Redux. Hear me out… React’s power is in it’s ability to reuse components and simplify things with containers. The more you refactor your code into smaller components, the more children you’ll need to pass props through to get data where it’s needed. I’ve tried several times to avoid Redux as long as possible and found adding it to be inevitable.

From a DRY perspective having to pass maybe 3, 4, 5 props down 3 or 4 levels for the data to reach where it’s needed, adds an enormous amount of bloat to every child down the line. With Redux you can refactor your components to your heart’s content, keep everything you need to access from random components in the store and only use local state for controlled components and the like.

Running Your App

To test your apps you need to install Xcode and Android Studio. You will need to install emulated OSes within each respectively that will be booted in a simulator to test your apps. It's also useful to use a real iPhone or Android phone by registering them within XCode and Android Studio for use. Plug them into your computer and enable developer mode.

react-native run-ios

Running the above command will trigger Xcode to compile, start bundling the JS, and then start the packager and testing server, which will allow you to hot-reload while making changes. Horray!

react-native run-android

This command will do the same, and will launch whatever simulator you’ve configured within Android studio.

Compiling your App for Clients

Android makes this fairly simple, IOS was a huge pain.

IOS

In order to compile your IOS app to send to clients or to publish to the app store you’ll need a valid developer license with Apple.

Getting your certificates and code signing set up properly will be a trial by fire and is out of scope of this guide. Good luck!

After “Archiving” which creates an IPA file, send it to some kind of app distribution or testing service.

Android

In order to sign your Android app for release you need to generate a keystore, and add it to your project’s keystore settings in Android Studio.

This will produce an APK file that can be easily emailed or linked to someone via the below distribution services.

At the bottom I have some bash commands to help streamline your release build process.

Distributing your App once Compiled

These services function much like alternative app stores, making it easy for your client to download the app, and easy for you to update the app for your client.

How they stack up against each other

At Gramercy Tech we developers use a combination of all of these.

I’m rooting for Microsoft App Center, but I’ve yet to get my project to successfully build on their remote server. The Microsoft team has been awesome and responsive so I’m certain I’ll get it working soon.

At the moment I am using TestFairy.

The pain of compiling (that web developers aren’t used to)

As you’ll soon see each build could take you upwards of 15–20 minutes as there are multiple steps that can take a while to do manually.

If I build both Android and IOS apps and upload them to TestFairy it takes about 30–45 minutes in total.

Compiling involves a lot of sitting around because I have to be there to initiate each additional step when the previous finishes.

I’m not able to go to lunch. I have to sit there.

I need to be able to compile and distribute with one command

The holy grail of local compiling right now can be achieved with Fastlane.

Fastlane is the tool to release your iOS and Android app. It handles all tedious tasks, like generating screenshots, dealing with code signing, and releasing your application.

Obviously though, something like Visual Studio App Center that will actually compile my app on their remote servers by simply updating a branch in my git repo is the future.

I’m barely able to use my computer while compiling, this frees that up. It also automates distribution to your team or clients.

End to End Testing and Mobile App Automation with Detox

From the creators of Detox:

High velocity native mobile development requires us to adopt continuous integration workflows, which means our reliance on manual QA has to drop significantly. Detox tests your mobile app while it’s running in a real device/simulator, interacting with it just like a real user.
The most difficult part of automated testing on mobile is the tip of the testing pyramid — E2E. The core problem with E2E tests is flakiness — tests are usually not deterministic. We believe the only way to tackle flakiness head on is by moving from black box testing to gray box testing. That’s where Detox comes into play.

I created an end to end test that would test my crud actions and auth flow. It starts by signing up and creating a new user, it then logs out, logs back in as that user, then navigates to the edit user page to completely change all of it’s properties to something new.

I found that good examples were really hard to come by so here’s the full test file:

Example E2E Test with Detox

Video of the automated test flow

I know that was kind of long, but here’s the video of this automation going through it’s sequence.

Animations in React-Native

Creating animations in React-Native is really awesome.

Here’s the code for my animated splash screen. As you can see I’m able to do this with Animated and Easing imported from React-Native and don’t need to import any additional libraries.

import { Animated, Easing } from "react-native";

The animation is an image of a ball that fades in, expands and contracts it’s size, and bounces from the bottom of the screen to the top.

I started by defining my animated starting values in the constructor.

Constructor

Animation Methods and Options

  • Animated.sequence([]) 
    Takes an array of animation events and performs them synchronously.
  • Animated.timing(property, { options }) 
    Lets you change gradually to another value over a duration of time.
  • Animated.spring(property, { options }) 
    Lets you expand and contract the size of the property.
  • Animated.parallel([]) 
    Lets you start two animations at the same time asynchronously.
  • useNativeDriver: true 
    Allows RN to render animations as performant as a standard native app

Start the Sequence in ComponentDidMount

I chain all these together to create a fun little ball bouncing sequence.

At the end of the sequence I’m even able to render a different animation conditionally based on whether the user is logged in or not, as well as forward the user to either the login screen or the main menu.

Render Method

There’s a little bit of setup in the render function that needs to happen first. I create plain objects with the style property I’m animating and set it’s value to the animated value I created in my constructor.

I then added that object to the special Animated.View and Animated.Image components. This allows the components to be controlled by animated values.

Bash Commands I use all the time

Cleans node_modules folder, reset caches, and re-installs node_modules folder using NPM:

rm -rf node_modules && npm install && watchman watch-del-all && rm -fr $TMPDIR/react-*

Cleans node_modules folder, reset caches, and re-installs node_modules folder using YARN:

watchman watch-del-all && rm -rf node_modules/ && yarn cache clean && yarn install && yarn start -- --reset-cache

Resets the React packager cache:

npm start -- --reset-cache

alternatively,

node_modules/react-native/scripts/packager.sh --reset-cache

Android Bash Commands

Create installable build:

react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/

Generate new Android Keystore:

keytool -genkey -v -keystore my-app-key.keystore -alias my-app-alias -keyalg RSA -keysize 2048 -validity 10000

Compile release build and run Android simulator:

react-native run-android --variant=release

Copy compiled apk directly to device that is connected to the computer:

adb install -r ./app/build/outputs/apk/app-release-unsigned.apk

Clean Android build folder (from android directory):

./gradlew clean

Clean and build Android Release (from android directory):

echo "DID YOU INCREMENT THE BUILD NUMBER?" && ./gradlew clean && ./gradlew assembleRelease

IOS Bash Commands

Record a video of the IOS simulator for clients:

xcrun simctl io booted recordVideo appvideo.mov

List IOS devices:

xcrun simctl list

Run IOS Device + Release configuration:

react-native run-ios --device 'YOUR-DEVICE-NAME' --configuration Release

Reset Everything and Update Pods (From the CocoaPods directory):

Update CocoaPods (from IOS directory) :

pod update
pod update repo
pod install --repo-update

Maintain Your Own Fork of a 3rd Party Component

Every time you clean your node_modules folder any changes you made to 3rd party components will wipe. To save yourself time, fork and maintain component you’ve edited so that each time you reset, npm will re-install the version of the component with your changes in it. You’ll be resetting everything quite frequently.

To install a component from your Github profile all you have to do is type:

npm install --save https://github.com/YOUR_GITHUB_USERNAME/PATH_TO_FORK/tarball/master

Adding /tarball/master allows it to install just like when using npm.

Here’s an example from my package.json of a 3rd Party component I forked with my own version as the source.

"react-native-circular-progress": "https://github.com/drumnation/react-native-circular-progress/tarball/master"

Your forked components will also need to get updates from the original app source, this won’t happen automatically anymore.

You will need to update your repo by merging changes from time to time, which will increment the version number of your fork and signal to your app that it needs to update to the latest version.

Add the remote from the original repo and fetch its updates.

git remote add upstream git://github.com/ORIGINAL-DEV-USERNAME/REPO-YOU-FORKED-FROM.git
git fetch upstream

Finally, update your fork from the original repo to keep up with their changes.

git pull upstream master

Other must read guides

Before you start building your React-Native project make sure you setup ESLint + Prettier. It will make your code super clean and save you a ton of time.

  • Configure ESLint, Prettier, and Flow in VS Code for React Development -> Link
  • Virtual DOM is the new IR -> Link
  • How to make an ARKit app in 5 minutes using React Native -> Link
  • Alphabetize your CSS properties, for crying out loud -> Link
  • Typed Redux -> Link
  • Inline React Styles Using the JSX Spread Operator -> Link
  • The State of Javascript 2017 -> Link

Conclusion

There are a number of differences between building a web application with ReactJS and building a Native app with React-Native, but at the end of the day you build pages with JSX, JSS, JS, reuse components, and structure things like a one page application in the same way.

As of writing this React-Native is still only version 0.51. I see immense value in the ability for Javascript developers to apply their knowledge of the React framework to creating Web, Desktop, and Mobile apps, cross-platform mobile apps at that. And since ReactVR is created with React-Native, I’m able to instantly apply this same skillset to building WebVR apps.

Pros

  • React-Native is as fast and performant as a typical native app
  • New features only need to be designed once in one language.
  • I was able to code the whole app with Javascript and CSS
  • Was able to make my App to look awesome on every device in existence.
  • Hot-reloading on multiple devices dramatically sped up development.
  • Used by Facebook, AirBnB, Instagram, UberEATS, Wallmart, Skype, Tesla.
  • Working alone, I was able to accomplish the work of at least 2 developers.
  • Maintained by Facebook (solid)

Cons

  • React-Native deprecated things that broke older 3rd party components
  • The packager and hot reloading could be finicky requiring lots of resets
  • I found myself “blowing in the Nintendo” so to speak, quite often, cleaning and resetting the node_modules folder via above bash commands.
  • Resetting the node_modules/ folder will remove any fixes I’ve added to React-Native as well as 3rd party components, which required me to re-apply hacks to fix RN bugs in my Xcode project settings constantly.
  • In the end I forked and maintained my own versions of 5 different 3rd party components so that I could keep them up to date and not have to constantly reapply fixes and customizations every time the “Nintendo needs a tap”.

This all said, without React-Native my project would have needed at least 2 people and I wouldn’t be one of them, since I come from web programming background, and mobile is a different universe.

React-Native really disrupts things in that sense, web developers getting into the mobile game. With React I’m able to develop applications for all devices in existence. Each project strengthens knowledge of the core React framework, which essentially has me learning and building my skills for making web apps even while I’m making native apps.

That’s really cool.