Over the past few days, I’ve had the opportunity to build a mobile application for both Android and iOS mobile devices. Constrained by time, and being familiar with React JS, I decided to implement the applications using React Native.

React Native is a javascript library authored by Facebook and released to the open-source community in the Spring of 2015.

Facebook was motivated to develop the library because they found their mobile development efforts were, at times, much slower than web development. The premise was to design a library which could be used with two very different development platforms, Android, and iOS.

React Native, in turn, allows engineers at MojoTech to develop cross-platform mobile applications with “Learn once, write anywhere” efficiency.

This post explores some of the ways in which React Native differs from React JS. Getting a mobile app up and running, in general, has its own learning curve. Fortunately, the React Native documentation details the tooling process, so I’ll limit dev-tool comments to important points and a couple of gotchas worth mentioning again.

5 Differences

(Ordered by workflow)

1 - Setup

Setting up the project from the command line makes life easy. The following two commands set up all the files needed for both Android and iOS deployment.

$ npm install -g react-native-cli
$ npm init react-native <YOUR_PROJECT_NAME>

To keep things organized I did set up a folder structure to house the application code: from the application root run

mkdir -p App/Components && cd App  
2 - Components

React Native uses pre-defined Components. Rather than <div>, you would now use <View> or <ScrollView> as the outer component wrapper. UI features are also rendered with specific component names, such as <Text>, <TextInput> and <Picker>. So now a render function might look like this sample:

render(){  
    return(
      <ScrollView style={styles.content}>
        <Text style={styles.heading}>Add Any Label Information</Text>
        <View style={styles.bordered}>
          <Text style={styles.listing}>1. Gauge - Gauge Units</Text>
          <SegmentedControls
            paddingTop={10}
            paddingBottom={10}
            containerBorderTint='#c1a7d3'
            tint="#5d4c8a"
            backtint='white'
            options={['inches', 'centimeters']}
            onSelection={this.setGaugeOption.bind(this)}
            selectedOption={this.state.gaugeUnits}
            extractText={(option) => option}
          />
          <View style={styles.row}>
            <Text style={styles.label}>Gauge:</Text>
            <TextInput
              style={styles.input}
              onChangeText={(gauge) => this.setState({gauge: gauge})}
              value={this.state.gauge}
              placeholder={'5.5'}
            />
          </View>
        </View>
        <View style={styles.bordered}>
          <Text style={styles.listing}>2. Type of Yarn</Text>
          <Picker
            selectedValue={this.state.yarnType}
            onValueChange={(yarn) => this.setState({yarnType: yarn})}>
            <Picker.Item label='Unknown' value='unknown' />
            <Picker.Item label='Sport' value='sport' />
            <Picker.Item label='Worsted' value='worsted' />
            <Picker.Item label='Bulky' value='bulky' />
          </Picker>
        </View>
      </View>
    </ScrollView>
  );
}

Notice the <SegmentedControl> component in the sample code above. This component is imported from the react-native-radio-buttons plugin, which clones the iOS segmented controls UI. This plugin styles radio-button functionality in a variety of ways and I ended up using it quite often.

When you browse around, you’ll find that there are a lot of plugins out there to extend React Native’s core library. The Android user interface benefits especially from component plugins. A great place to search for both React and React Native packages is https://js.coach/

3 - Navigation

On the other hand, Navigation is part of the React Native Core library. It comes in two flavors, Navigator and NavigatorIOS. The React Native team supports the use of Navigator, but most tutorials out there demonstrate NavigatorIOS due to its simplicity.

React Native supports platform-specific development through .ios.js and .android.js file extensions. Originally my idea was to use React Native’s <Navigator> component for both platforms. However, since I needed to use special plugins for the Android UI, the codebase for the two platforms ended up diverging anyway and I decided to use both, which I could now do easily using the appropriate extension.

The first two code snippets below are examples of iOS specific code using the <NavigatorIOS> component:

//index.ios.js

import React, {  
  AppRegistry,
  Component,
  NavigatorIOS
} from 'react-native';

import Main from './App/Components/main';  
import { styles } from '../styles';

class KnitWhiz extends Component{  
  render() {
    return(
      <NavigatorIOS
        style={styles.container}
        initialRoute={{
          title: 'KnitWhiz',
          component: Main
        }}
      />
    );
  }
}
// main.ios.js
      .
      .
handleSubmit(){  
  this.props.navigator.push({
    title: 'Yardage',
    component: ShowYardage,
    passProps: this.calculateBySkein()})   //object literal
}
      .
      .
render(){  
   return(
     <View style={styles.container}>
       <TouchableHighlight
          style={styles.submitButton}
          underlayColor="#5d4c8a"
          onPress={() => {this.handleSubmit()}}>
          <Text style={styles.buttonText}>Calculate</Text>
        </TouchableHighlight>
      </View>
   );
}

And these next two are code examples of navigation with <Navigator>.

//index.android.js

import React, {  
  AppRegistry,
  Component,
  TouchableOpacity,
  Navigator
} from 'react-native';

import Main from './App/Components/main';  
import Category from './App/Components/category';  
import WrapsEstimator from './App/Components/wraps_estimator';

class KnitWhiz extends Component{  
  constructor(props){
    super(props);

    this.renderScene = this.renderScene.bind(this);
  }

  render() {
    return (
      <Navigator
          initialRoute={{name: 'Main', title: 'KnitWhiz', passProps: {}}}
          renderScene={this.renderScene}
          configureScene={(route) => {
            if (route.sceneConfig) {
              return route.sceneConfig;
            }
            return Navigator.SceneConfigs.FloatFromRight;
          }} />
    );
  }
  renderScene(route, navigator) {
    switch(route.name) {
        case 'Main':
          return <Main navigator={navigator} {...route.passProps} />;
        case 'Category':
          return <Category navigator={navigator} {...route.passProps}/>;
        case  'Wraps':
          return <WrapsEstimator navigator={navigator} {...route.passProps}/>;
        default: 
          return <Main navigator={navigator} {...route.passProps} />;
    }

    return (
      <View style={{flex: 1, justifyContent: 'center'}}>
        <TouchableOpacity style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}
            onPress={() => navigator.pop()}>
          <Text style={{color: 'purple', fontWeight: 'bold'}}>Home</Text>
        </TouchableOpacity>
      </View>
    );
  }
}
//main.android.js

var React = require('react-native');

import NavButton from './nav_button';  
import Category from './category';  
import WrapsEstimator from './wraps_estimator';  
import SkeinEstimator from './skein_estimator';  
import SwatchEstimator from './swatch_estimator';  
import { styles } from '../styles';

const {  
  Navigator,
  TouchableOpacity,
  View,
  Text
} = React;

class Main extends React.Component{  
  constructor(props) {
    super(props);

    this.renderScene = this.renderScene.bind(this);
  }

  handleNavigation(next_component, next_title) {
    this.props.navigator.push({
      title: "Sweater Info",
      name: 'Category',
      passProps: {
        nextComponent: next_component,
        nextTitle: next_title
      }
    });
  }

  render(){
    return(
      <Navigator
        renderScene={this.renderScene}
        navigator={this.props.navigator}
        navigationBar={
          <Navigator.NavigationBar 
             style={{backgroundColor: '#5d4c8a'}}
             routeMapper={NavigationBarRouteMapper} 
          />
        }
      />
    );
  }

  renderScene(route, navigator) {
    return (
      <View style={styles.mainContainer} >
        <Text style={styles.heading1}>How Much Yarn Do You Need?</Text>
        <NavButton
          onPress={() => {this.handleNavigation('Wraps', 'Wraps Info')}}
          text="Wraps"
        />
        <NavButton
          onPress={() => {this.handleNavigation('Skein', 'Skein Info')}}
          text="Skein Info"
        />
        <NavButton
          onPress={() => {this.handleNavigation('Swatch', 'Swatch Info')}}
          text="Swatch Info"
        />
      </View>
    );
  }
};

var NavigationBarRouteMapper = {  
  LeftButton(route, navigator, index, navState) {
    return null;
  },
  RightButton(route, navigator, index, navState) {
    return null;
  },
  Title(route, navigator, index, navState) {
    return (
      <TouchableOpacity style={{flex: 1, justifyContent: 'center'}}>
        <Text style={{color: 'white', textAlign: 'center', marginLeft: 40, fontSize: 20}}>
          KnitWhiz Tools
        </Text>
      </TouchableOpacity>
    );
  }
};

export default Main;  

Highlighting the differences...

  • NavigatorIOS
    • Uses a ‘component’ property for identifying routes
    • Automatically configures top-bar navigation using ‘title’ property
    • Passes props from screen to screen with a ‘passProps’ property.
    • Only available for iOS apps.
  • Navigator
    • May use any property for routing
    • Separates navigation from rendering (with a specific sceneRender function)
    • Allows for “Right” and “Left” buttons in top-bar navigation (Android)
    • Passes props inside Navigator’s route property (which may be named anything -- here I’ve used “passProps” for consistency)
    • May be used for both Android and iOS apps.
  • Both
    • Require an initialRoute prop

On a separate client project at MojoTech, we ended up using and contributing updates back to the react-native-simple-router project. This is a nice, small wrapper around the core React Native Navigator component and allowed us on that project to use a single Router Navigation system for both iOS and Android without diverging code.

4 - Styling

The next departure from React is the use of javascript rather than css for styling, through the style prop. React Native styling via the StyleSheet component isn't pure CSS that you are used to from the web, but a subset of them via an implementation of Flexbox.

Most tutorials tack on the styles used below the class definition, while I personally prefer to import them from a single external file. In ES6, for example:

// App/styles.js

'use strict'

const React = require('react-native');  
const { StyleSheet } = React;

export const styles = StyleSheet.create({  
  mainContainer: {
    flex: 1,
    marginTop: 55,
    flexDirection: 'column',
    justifyContent: 'center',
    backgroundColor: '#E8DDF4'
  },
  content: {
    flex: 1,
    padding: 40,
  },
  wrapContent: {
    flex: 1,
    padding: 40,
    marginTop: 50
  }
});

Styles may then be imported where needed:

import { styles } from '../styles'

Although the decision to go with a specific javascript “subset” of css styles caused me a bit of pain when I chose a style property which didn’t exist, e.g. a border property, the pain was ameliorated by a handy list of substitutes displayed within the error message, e.g. a borderWidth property.

And finally…

5 - Simulation & Deployment

The documentation does a good job of walking you through getting started with Android. I found it fairly straightforward, with one caveat: after configuring a physical device to use for testing, be sure to stop the Android AVD GUI as only one device may be used for testing. In other words, you won't be able to test simultaneously on an Android emulator and a physical device. Another alternative, Genymotion, is also a great simulator to use.

Once the app was tested on a device, actually deploying to Google Play required only following a few instructions and paying a few dollars. You can get started with Google play at https://support.google.com/googleplay/android-developer.

Apple requires a few more dollars and an iTunes Connect account. A good place to start is https://developer.apple.com/programs.

Summing Up

Using React Native to get a mobile app up and selling was an efficient and thoroughly enjoyable experience for me. Here's wishing you all the best with your next app!

Cathy Zoller

P.S. We're ramping up our engineering team! Check out our open positions in Boulder and Providence.