Coding Apps with React Native at Exponent
If you haven’t built an app before it can be intimidating because there is a lot of unfamiliar territory. Before you get a few years of experience specifically with mobile, it can be helpful to have some advice on what to do and why to do it. And for many people reading this who work on software products and have a career ahead of you, mobile expertise is really important when you think about how people are spending their time and what the next billion computers will look like (hint: Android).
At Exponent our primary focus is mobile and we’ve learned a lot about making apps. These are some of the ways we think about and approach app development, specifically with React Native.
Code Style
We use modern JavaScript, specifically parts of ES2015 and ES2016 that are enabled by React Native’s packager by default, with a few extra features like ES2016 decorators.
Generally, we want our code to be easy to understand and easy on the eyes, and we are pragmatic about how we use JavaScript.
- We use functional programming in the high-level structure of our programs by using React and Redux. Chains of map, filter, and reduce calls aren’t what’s most important.
- We use “let” or “var” instead of “const” in most places except for actual constants. “let” looks nicer and reassigning to a variable within a function isn’t a source of bugs for us.
- New JS features give us more options when writing code but don’t necessarily supplant or deserve more use than old ones
- Priorities: make it work, make it right, make it fast
Modules
We usually use official JS modules with the “import” and “export” keywords instead of CommonJS modules that use the “require” function and “module” object. The two are interoperable but JS modules meet most of our needs and, by default, handle cycles better than CommonJS modules.
React Native’s packager also supports a Facebook-proprietary directive called “@providesModule” that lets you name your module in a way that’s accessible from anywhere within your package. So, we’ll write:
/** * @providesModule Api */ 'use strict';
import encodeUriParameters from 'encodeUriParameters';
const API_ORIGIN = 'https://api.example.com';
export default { async callAsync(method, args) { let url = `${API_ORIGIN}/${method}?{encodeUriParameters(args)}`; let response = await fetch(url); return await response.json(); }, };
Async Functions
Async functions are finally coming to JS and React Native supports them. Several other languages like C# and Hack have supported async functions for awhile so it’s nice to see them in JS, too. They’re better for writing servers than UIs right now, but we still find them useful in apps.
One convention we use that is more important than it may first look is to suffix all async functions with “Async”. This makes it very clear when we need to await the result of the function without extra static typing, tooling, or other overhead.
async function logAsync(data) { let response = await fetch(LOG_ENDPOINT, { method: 'POST', body: data, }); if (!response.ok) { let errorText = await response.text(); throw new Error(`Failed to log data: ${errorText}`); } }
React Components
All of our React components either use JS’s class syntax or are stateless function components. We mostly use classes because our components are stateful, declare prop types, or have convenient helper methods.
Within classes, we use static class properties to declare prop types:
class Box extends React.Component { static propTypes = { size: PropTypes.number, }; }
One of the best features of prop types is that you can gradually increase their precision as your code becomes more stable. At first you can specify just the props that you think someone is likely to forget or misspell. The types don’t need to be precise either; “PropTypes.any” or “PropTypes.object” go a long way. As the component matures, it makes more sense to spend time filling in the prop types more completely. Prop types also act as a lightweight form of documentation.
Documentation
Accuracy of our documentation is important so internally we lean towards reading code, the source of truth. (For public developer-facing APIs we will author more formal documentation.) When a piece of code has far-reaching side effects, comments are helpful for raising awareness of the fuller context.
For processes like setting up our development environment or releasing a new version of our code, we write readme files to explain how the processes work.
File Organization
Our files are generally organized by feature — what they do instead of what they are. A chat app, for example, might have directories named “profile”, “inbox”, and “conversation”. We don’t have directories named “models” and “views” except for general utilities or components that help us write models or views.
App Structure
Many apps have a tab bar and allow navigation within each tab’s scene. They also support sliding scenes up from the bottom.
We use a TabNavigator for the tab bar and nested ExNavigators for both navigation within tabs and covering the whole screen with scenes.
Since TabNavigator and ExNavigator (and its underlying Navigator, which does most of the work) are written in JS, they work on both Android and iOS.
Data Flow
We use Redux and its React bindings with decorators. For high responsiveness, we efficiently avoid unnecessary render passes by usingImmutable data structures. The verbosity of Immutable’s data structures is unpleasing so we looked into seamless-immutable but switched back to Immutable for its lazy sequences. We sometimes also use Reselect with a custom Immutable-aware selector creator so that we test for value equality instead of reference equality.
Example File
This is an example file demonstrating how we write a module that exports a React component:
JavaScript Preferred
We greatly prefer components that have a consistent API across platforms. Often this means using components written in purely in JS, but it also includes components that present a unified API on top of their native implementations, like View or ScrollView.
JS components are also faster to iterate on in several ways. This lets us polish their behavior more quickly and build components that are comparable in quality and superior in flexibility compared to their system-native counterparts.
Coherent Design
One of our design principles is to give people a coherent, self-consistent experience. We are able to do this best when we can fit our entire vision for the app’s design in our head. This is much easier when there’s only one design instead of two so we aim for a single great design over iOS’s design or material design; that said, there are great ideas in Apple and Google’s design guidelines and we thoughtfully follow them when it makes sense.
Many of the most important apps take this approach. These are two of them:
People with Android phones understand how to use Instagram and people with iPhones understand how to use Google Maps. We expect the designs of more apps to converge as this trend continues.
Evolution
We are always changing how we do things as we build for more use cases. In the next couple months we expect ExNavigator and TabNavigator to undergo significant API changes and work together. Perhaps the most important aspect of this evolutionary development is that we build for our own, real-world use cases. That’s the main reason I believe many of our components and the points covered in this post will work for other people. Similarly, one of the most important features of React Native is that its purpose is to build Facebook; React Native is a means to an end, not the end itself.
It’s been less than a year since React Native was publicly released and we’re already seeing great interest in the technology. Over the next two to three years as Exponent and React Native mature and stabilize, we see the mobile development landscape being in an even better place than we would have expected otherwise.
Special thanks to Nikhilesh Sigatapu for proofreading a draft of this post.
If you liked this post, recommend it to others by clicking the button below. Follow @exponentjs on Twitter for updates and what people are doing with Exponent.