Developing Mobile Apps with React Native When You Only Know React

This quarter I spent 12 weeks learning React Native by building a Slack-like chat app. As someone who writes React every day, I was excited by React Native’s promise: learn once, write anywhere. In the following article, I will discuss how you can quickly get started with React Native and whether it is worth exploring the new framework to begin with.

The Fault in Our Web Technologies

While React Native feels brand new in terms of the amount of attention it has been getting, it is already reasonably old. Facebook announced React Native at React.js Conf 2015. In the keynote, software engineer Tom Occhino discusses native code’s merits by asking why no one has succeeded at making an app that feels as good as Facebook paper* using only web technologies. 

According to Tom, one of the reasons that no one has achieved this is because native code enables us to parallelize work. Because we can parallelize work, we can ensure the smoothness of complex animations by dealing with rendering off the main thread. Besides this, Tom claims that it is easier to work elegantly with gestures in native and that it is challenging to mimic native components on web because they are black boxes. With an already impressive list of advantages of native over web on the screen, Tom adds that it is impossible to access specific frameworks, such as MapKit, on web. As a result, we can not give people the same map experience using web technologies.

*To follow his argument, it is good to know that Facebook paper, which does not exist anymore, was an app that turned Facebook into an interactive newspaper. Mike Matas, one of the designers who worked on the UI for the original iPhone, led Paper’s design team. Coincidentally, Paper had what many consider to be the most beautiful interaction patterns and animations an app has ever had.

Facebook software engineer Tom Occhino introducing React Native

Understanding the Need for React Native

If native is so much better than web, why do we still seem to long for ways to build apps using web technologies? Why does there seem to be a demand for frameworks such as Cordova? It is because, as Tom explains, native’s power comes with a lot of complexity. Engineers have to think about topics such as memory management, and tasks such as deploying are everything except straightforward.

As you might be able to infer from this buildup, there is a gap: we like our native code’s results but have to jump through hoops to get there, and while we love the ease of working with web technologies, there is no way that web-based apps can feel as good native. But what if there was a way to use the declarative React API we know and love, and ultimately only get native code and thus, native results? Would that not be awesome? It would be. And that, as Tom explains in the very same presentation, is the idea behind React Native.

Diving into React Native’s Internals

As someone who prides himself on the speed at which he can pick up a new framework or tool, I am good at using something without understanding what is going on under the hood. The beauty, and perhaps danger, of React Native is that you do not have to know how it works to to use it. After all, it exposes what is virtually the React API you are familiar with. Nonetheless, I think it is good to at least get a rough idea of what is going on because there will be a point in time at which you have to convince others, such as your engineering manager, to let you use React Native. 

The most important thing to understand is that the components you see on your screen and interact with are all native components; React Native does not use web views. Instead, your JavaScript code drives the creation of native components as well as updates to them. This JavaScript code runs on a separate thread and is in most scenarios executed by a JavaScript engine called JavaScriptCore. The JavaScript realm and the native realm communicate with each other using a so-called “bridge.”

Driving native code using JavaScript that runs on a separate thread is not a perfect solution by itself. To be performant, React Native batches React updates. The JavaScript and native realm communicate asynchronously, meaning that either side does not execute tasks immediately, but when they see fit. These optimizations are incredibly similar to what React does on the web. While the result is a very fast framework, it does not mean that React Native cannot be slow. The messages that are sent over the bridge need to be serialized for the realms to understand each other. Serialization is expensive, and that becomes apparent when a lot of traffic needs to go over the bridge.

A Time and Place for Native

While I like to proclaim JavaScript superiority, there is a time and place for native. Maybe you are reading this article, but do not feel like migrating your entire Java codebase to React Native. Or perhaps you need to access that new Apple feature for which there is not yet a React Native module.

Luckily, it is not an either/or situation, and you don’t have to choose. It might require some extra effort, but React Native can work seamlessly with existing codebases. In fact, companies like Airbnb and Facebook combine native code with React Native all the time.

Spinning Up A React Native App

At this point, you might want just to jump in and get started. If you usually write React, you likely do not do any setup work yourself, but instead, rely on a tool like create-react-app to get started right away. The good news is that there is a React Native equivalent called create-react-native-app (CRNA).

Creating a React Native app using CRNA. From https://github.com/react-community/create-react-native-app

CRNA is a partnership between Facebook and Expo. Expo is a company that tries to help people build apps using JavaScript and React through React Native tooling. In practice, that means that out of the box CRNA uses the Expo client, which enables us to develop React Native apps without Android Studio or Xcode. On top of this, we can use the majority of Expo APIs, which provide us with access to native modules such as maps. 

You eventually might find that CRNA does not fit your needs because it is limited in what it supports out of the gate. For instance, you might want to use custom native code or use Expo features that you do not get by default, such as a way to quickly add push notifications to your app. If that is the case, you have a variety of options available to you. One of them is ejecting, as create-react-app also allows you to do.

React Native Is Almost Too Easy

The main problem I initially had with React Native is that it is almost too similar to React. As a result, I was always wondering whether I was doing things right or relying too much on my experience with React on the web. Truth be told, Facebook did a great job at handling the complexity for you and, even if you end up working with Redux, there will only be a few differences between your React and React Native code. 

Components and Their Web Equivalents

While both the resulting code and API are different, it is useful to think of React Native’s essential components in terms of what would be their web equivalents. Pretend they just have a different name, like Fogell in the movie Superbad. A div is now a View component, a paragraph is now a Text component, and an img is now an Image component.

React Native is almost too similar to React

The Web Analogy Does Not Always Hold Up

As we have just seen, we can get started with React Native code by thinking of several React Native’s components as html tags. However, that is not to say that there are not a few tricky differences between React Native and regular React. Most notably, the default flex direction is “column” instead of “row”. We also can not use svgs as quickly as we usually would.

Styling React Native Apps

There are a variety of ways to style React apps. For example, you can use a CSS style sheet, write inline styles, or use CSS-in-JS. While you can also write inline styles in React Native, Facebook recommends using the StyleSheet helper. The StyleSheet helper has a create() method that accepts a plain JavaScript object which’s key and value pairs together describe a single component’s styles. You can then apply the resulting style reference’s styles to the appropriate subcomponents, optionally mimicking the way styles cascade in CSS. It is important to note that you are writing JavaScript and not CSS. For this reason, the keys are camel-cased CSS property names. The values are strings that accept most of the usual CSS values.

Styling a React Native component using the StyleSheet helper. Adaptation of https://facebook.github.io/react-native/docs/stylesheet.html

Since React Native encourages you to think of a component and its styles as one entity, CSS-in-JS is only a small step away. In fact, you can use styled-components the same way you normally would and even use styled-system to enforce your design system.

Styling a React Native component using styled-components and styled-system. The second import statement is the only React Native specific line

Handling Navigation in React Native Apps

React Navigation is currently the de facto standard for managing navigation in React Native, although there are many alternatives out there. Facebook recommends it to people who are new to React Native since it comes with the most popular navigation solutions, such as a tab bar, and consists only out of JavaScript. The latter enables you to you to use it with Redux.

The library has a relatively straightforward API and indeed gets you up and running with frequently used navigation elements such as a drawer in a heartbeat. Nonetheless, I disliked my experience with it. It is primarily easy to get frustrated with React Navigation because you can unintentionally lose track of your app’s structure if you nest navigators. My problems were mostly related to using it in combination with Redux, and I would not integrate the two unless you have a good reason to do so. All in all, React Navigation does what it promises, and the documentation has a good pitch and anti-pitch, in case you are interested.

Building More Complex Apps with React Native

Once you can create components, style them, and handle navigation, there are only a few more topics you need to know about to build reasonably complex apps with React Native. Namely, networking, sessions, and push notifications.

Networking

Interacting with APIs and WebSockets is what the official React Native documentation calls networking. It is here that the lines between React and React Native blur even more. Fetching and posting data works the same way as in React, and you can rely on third-party libraries such as axios if you like. You can also keep using WebSockets in the same way. Hence, I used my regular Redux middleware to process any incoming WebSocket messages. The only networking difference I found between React and React Native is that I was not able to simulate an https connection using a self-signed certificate. However, I did not take the time to debug that issue carefully.

Sessions

React Native offers a local storage system called AsyncStorage that works in the same way as HTML5 localStorage. The main difference is that AsyncStorage, as the name implies, is asynchronous. Information stored in AsyncStorage persists even when the user quits the app or restarts their phone. For this reason, AsyncStorage is the perfect place to store information that indicates whether users have finished onboarding or not as well as keep their actual session tokens.

Persisting data using AsyncStorage. From https://facebook.github.io/react-native/docs/asyncstorage.html
Fetching data from AsyncStorage. From https://facebook.github.io/react-native/docs/asyncstorage.html

If you store the user’s session token in AsyncStorage, there will be a period in which you have to retrieve the token from AsyncStorage and subsequently navigate the user to the correct screen based on whether they are signed in or not. You can decide on a default screen, such as the login screen, and quickly redirect the user when it turns out they were already signed in. That will result in a brief flash, and it is therefore much better to use a splash screen. Expo’s AppLoading component is one way of creating such splash screens.

There might also be times that you find yourself wishing that the information stored in your Redux store, which resets every time the user quits the app, persisted. For instance, you might want to save part of the user’s profile so that you do not have to retrieve it every time. The Redux Persist package does just that. Every time your reducers recalculate your app’s state, Redux Persist saves any relevant state to AsyncStorage. When your app boots up, redux-persist uses the Redux data found in AsyncStorage to rehydrate your store. Redux Persist is incredibly useful but requires care. (Preventable) problems can arise when Redux Persist rehydrates your store with an old piece of state, but you have changed this piece’s structure and the code that works with it. 

Push Notifications

Even though a good chat app has push notifications, I did not have time to implement them. Some quick research did convince me that Expo provides you with an easy way to use push notifications. All you have to do is obtain an Expo push token for the user and save it, call Expo’s push API with the token, and (optionally) handle incoming push notifications. There are two caveats here. First, you have to open your CRNA app with the Expo XDE (Expo Development Environment) if you want to implement push notifications through Expo. Second, simulators can not receive push notifications, so you will have to test your push notifications on a real device.

Debugging Your React Native Code

By default, you will have access to the Chrome Developer Tools and can debug your JavaScript code in the browser. If you want to have more control while debugging and for instance want to inspect the React component tree, you have to install additional packages. React Native Debugger is a debugger I highly recommend since it combines three of the most useful React Native debugging packages into a single app.

React Native Debugger combines three of the most useful React Native debugging packages into a single app

The Write Once, Use Anywhere Pipe Dream

In the React Native announcement talk, Tom Occhino explains that the React Native team is not chasing the “Write once, use anywhere” pipe dream because every platform has its own look, feel, and principles. Instead, React Native strives to support the “Learn once, use anywhere” paradigm. For iOS and Android, this has few implications. You still write most of your React Native code once and only occasionally write some platform specific code, mostly to meet each platform’s visual expectations.

While not necessarily sharing code means little if we are just making a React Native app, it starts to become a more significant issue as the number of React platforms we want to support increases. For instance, nowadays we might wish to support React VR, which Facebook launched in April 2017. Do we need to write and update every component in at least three locations if we want to support web, native, and VR?

At this point, you might be thinking to yourself, “there has to be a better way.” And maybe there is. In 2016, Airbnb engineer Leland Richardson came up with the idea of React Primitives, which he based off React Native Web. He discovered that when people use React Native, 82% of the time they are only using six components (Animated, StyleSheet, View, Text, Image, Touchable), or primitives. In other words, if you make an API that knows how to translate those components to different platforms, you can make a large part of your code base universal.

The official React Primitives example that works across platforms. From https://github.com/lelandrichardson/react-primitives

React Primitives brought with it a (renewed) interest in universal React. For example, Airbnb released a library that can significantly alter the way engineers and designers work together by making it possible to render React components in Sketch. Presently, some companies, such as MLS, are even using universal components in production. Nonetheless, I remained skeptical of truly cross-platform React and removed React Primitives from my app’s codebase. It is a great idea, but it requires a group of contributors to step forward and invest significant amounts of time into maintenance and updates. React Native Web, which is getting better every day, proves that can happen. However, React Primitives has a large number of issues that the authors have not addressed in months.

Airbnb engineer Leland Richardson discussing cross-platform React

Should You Use React Native?

After 12 weeks of developing in React Native, I was admittedly mostly frustrated. React Native had enabled me to create apps for both iOS and Android, but the development experience was far from perfect. Many days were spent looking up cryptic error messages on StackOverflow and dealing with simulators that did not work correctly.

Looking back, the mistake I made was to assume that the React Native development experience would be identical to the React development experience. While it is in terms of API, it is not in terms of the quality of tooling and documentation. That is not completely surprising considering that React is two years older and Facebook likely has more resources to dedicate to development of the core library, React, than a framework that uses it.

Now that I have had some time to reflect, I would highly recommend using React Native. The development experience was not perfect, but even with all the hours spent debugging tools, I still would not have gotten as close to developing a cross-platform app as I did had I tried to learn both Swift and Kotlin instead. 

What I especially love about React Native, is how it builds bridges. The idea of taking a declarative API people already know and using it to communicate with more complex systems is simple yet effective. All of the sudden frontend developers, mobile developers, and designers can collaborate much easier, and companies’ costs can go down.

The Future of React Native

With the speed at which our industry is moving, it is not unlikely that React Native will not be around anymore in two years. The ideas it has made us discuss will keep evolving, and regardless of what we will be using in two years, it makes sense to be able to contribute to mobile development with varying degrees of understanding of native code. New competitors like Flutter proof that React Native has hit the right note.

At least for now, React Native is not going anywhere. In fact, it looks like React Native is only going to get better from here. I am personally especially excited about the idea of developing React Native apps using Reason, but that is a whole other article…

Resources

In most cases, the official documentation will suffice. Like this article, the website React Native Express focuses on getting you started with React Native as quickly as possible. The author also co-wrote a book, which could be worth the investment.

When I was learning how to add more advanced features, such as a map and Facebook authentication, to my app, I enjoyed the course React Native: Advanced Concepts by Stephen Grider. It provides you with step-by-step walkthroughs of how to add such functionality.

Lastly, every year, Facebook open sources the React Native app they create for their F8 development conference. It is an excellent example of what a real React Native app might look like.

References

The resources I used to write this article besides the ones linked in the text