Kyle Banks

React Native.png

React Native is all the rage these days, allowing developers to write native Android and iOS applications with JavaScript, based on the React framework for the web. React was created by Facebook in order to better develop large-scale, stateful web applications, and React Native is their solution for writing native mobile applications with a single codebase.

React Native is relatively young, but it provides a feature-rich API and a vast array of tools for developers to improve productivity, and there are already thousands of production applications developed with it. These aren't small apps either; we're talking the likes of Facebook, Instagram and Airbnb! While I've admittedly been skeptical of similar frameworks in the past, assuming they were the lazy way out of learning the native languages for each platform, it's tough to ignore the power and flexibility of React Native, and the proof is in the form of large-scale production applications using it.

In the following tutorial, we'll cover developing React Native applications for Android—but the same codebase will also run on iOS devices if you have one available. While this tutorial is aimed at developers with no React or React Native experience, it does assume some limited JavaScript and Android development experience.

Introduction

React Native tutorial application ReactUsers demo

For this tutorial, we're going to develop an application that displays an infinitely scrolling list of users fetched over the network, with the ability to select a user from the list to see their full details. We'll be using the free and open-source RandomUser.me API as our back end, and we'll perform network requests to the API in order to retrieve randomly generated users which will then be displayed in a list view in the application.

We'll be covering common requirements for mobile developers, including touch input, laying out and styling views, making API requests, and actually deploying the application to Android devices. At the end of the tutorial, we'll also cover some of the debugging tools React Native provides us in order to give you some comfort when you go out and develop your own React Native apps.

The full code for the tutorial is available on GitHub, so don't hesitate to take a peek or send a pull request if anything is unclear.

GitHub Link

Installation

We'll be developing our application using React Native version 0.36.0, the latest at the time of this writing, but we'll be sticking to relatively standard functionality of the framework, so backward compatibility in future versions should (hopefully) not break the codebase. I'll also be assuming that you have a working Android development environment set up on your machine, but if not, head over to developer.android.com to get a setup.

The React Native installation is platform-dependent, meaning the process varies based on the OS you're developing on. I'll go over the Mac OS installation here, but take a look at the Getting Started guide for your platform of choice.

For developers on Mac OS, you'll need Node.js and Watchman installed in order to develop with React Native:

brew install node
brew install watchman

With those dependencies installed, you'll now be able to install the React Native CLI using npm:

npm install -g react-native-cli

Once that completes, go ahead and run the react-native -v command to ensure you're all set to go:

$ react-native -v
react-native-cli: 1.0.0

Note that you may see an error mentioning that you're not in a React Native project directory. Don't worry about that for now; it's to be expected since we haven't actually created a project just yet.

Hello, React

With the React Native CLI installed, we're ready to create our first React Native application. The CLI provides everything we need to generate a boilerplate React Native project, which will suit us for the remainder of the tutorial.

A project is created using the init command and by specifying the project name. For this tutorial, we'll use the clever and original name, ReactUsers:

$ react-native init ReactUsers

The first time you run the init command, the CLI will download and install its required dependencies, so you may find that it takes some time. Once it's complete, you should have a new ReactUsers/ directory where you ran the init command. Let's take a peak inside and see what was created for us.

You'll notice a few files and directories were created. The android and ios folders contain the standard Android and iOS development projects you would expect to find when developing native applications, and can be opened in Android Studio and Xcode respectively. You'll also notice a node_modules directory and a package.json file; if you're familiar with Node.js, then these should be familiar to you. If not, node_modules is essentially just a bucket for all your Node.js dependencies, and package.json describes both your project and the dependencies that it requires.

Finally, you'll notice index.android.js and index.ios.js files: These are our first React Native source files! React Native uses the same import mechanism as Node.js, comparable to an import in Java, and will go through a couple steps when searching for a file to import. Let's say we want to import a file called example:

import * from './example.js'

React Native will first search for a file that is suffixed with the name of the current platform that you're running on—for example, example.android.js when running on Android devices. If it cannot find this file, it will next search for a file without the platform name—in this case example.js. This allows you to write platform-specific code when necessary, and shared code when the logic and contents are the same across platforms.

The index.*.js files are the only exception, you must have an index.android.js file to run on Android devices, and an index.ios.js file to run on iOS devices. Typically what I do is have both of these files simply require a shared main.js source file that contains the root of the application logic, and begins initializing the application as required.

But before we start writing any code, let's take a look at the contents of index.android.js. Open the file in your editor of choice, and you should see something like so:

// index.android.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';

export default class ReactUsers extends Component {
  render() {
    return (
      <View style={styles.container}>
          <Text style={styles.welcome}>
              Welcome to React Native!
          </Text>
          <Text style={styles.instructions}>
              To get started, edit index.android.js
          </Text>
          <Text style={styles.instructions}>
              Double tap R on your keyboard to reload,{'\n'}
              Shake or press menu button for dev menu
          </Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('ReactUsers', () => ReactUsers);

Don't worry if this looks foreign to you, as we'll be deleting it all and starting from scratch shortly. What's important here is that we're going to run this sample application on an Android device, and you should take a moment to match what you see on your screen to the contents of this file.

Running on your Android Device

The React Native CLI comes with a run-android command to package and deploy your application to an Android device or emulator. There's also a corresponding run-ios command, but we'll be focusing on Android from here on out.

At this point, you should have an Android device plugged into your computer, or an emulator running. Once you're set, go ahead and run the following command from the ReactUsers/ directory:

$ react-native run-android

You should notice a second terminal window open that runs the React Native packager application. This is tremendously useful for development because it watches for JavaScript source code changes, and repackages and deploys the changes to your device as you develop your application. For now, simply minize the window and allow it to run behind the scenes.

Once the packager and CLI have bundled and installed the application on your device, you should see the following:

React Native tutorial Hello World

Take a moment to look at the contents of index.android.js and see if you can line up the contents with what you see on your screen. Again, if this is all foreign to you, don't worry as we'll be starting from scratch in the next section.

Project Setup

Alright, let's take it from the top. Go ahead and clear the contents of index.android.js so we have a blank canvas to start from.

The first thing we'll want to do is import a few classes, namely React, Component and AppRegistry. The React and Component classes are found in the react module, and AppRegistry is found in the react-native module. Here's how the imports look:

// index.android.js

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

Next we'll define and export our ReactUsers class below the import statements, which will act as the entrypoint for our application:

// index.android.js

export default class ReactUsers extends Component {
  render() {
    return ""
  }
}

As you can see, we extend the Component class, which is React's concept of a view/controller hybrid. We implemented the render method, which is used to return the view of the component. But for now we're rendering nothing but an empty string. We'll come back to this shortly.

Finally we need to register our ReactUsers component with the AppRegistry in order to tell React that this is our root component.

// index.android.js

AppRegistry.registerComponent('ReactUsers', () => ReactUsers);

If you run the application at this point, you should see nothing but a blank screen, because we aren't actually rendering any content.

Introduction to Views and Styles

Let's create our first view. We'll start off with a simple header at the top of the screen to display the application name. First, create a new directory called src so we can keep the project structure clean. This isn't required by any means, but it helps reduce clutter in the root of the project.

Next, create a header.js file in the src directory. Here's where we'll define the Header component. We'll start again by importing the required classes to define and style our component:

// src/header.js

import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  View
} from 'react-native';

You'll note that we're not importing the AppRegistry in this case because the header is not our application entrypoint. Additionally, we're importing the StyleSheet, Text and View classes from the react-native module. The StyleSheet class provides a styling system comparable to CSS, and the Text and View classes represent components of the same name that we'll use to create our header component.

Next we'll want to define the Header class, and again extend the Component class. Note that this time we are adding some content to the render method to define our view:

// src/header.js

export default class Header extends Component {
    render() {
        return (
            <View>
                <Text>React Users</Text>
            </View>
        )
    }
}

Within render, we return a View component with a nested Text component. The View will act as a container with the Text centered inside to render the application name.

Next we'll need to actually render our custom Header component from index.android.js. First we need to import the Header class:

// index.android.js
import Header from './src/header';

And now we can use it from the render method of the ReactUser component:

// index.android.js

render() {
    return (
        <Header />
    )
}

If you run the application now, you'll see a very plain-looking label that simply says "React Users", but it's a start. Next let's actually style the header to give it some personality.

Styling

You might recall that we imported the StyleSheet class in the header.js source file. React Native supports a limited subset of CSS styling properties that can be defined in JSON "stylesheets" and used when rendering components. We'll use this to define some styles for our header component.

Back in header.js, let's define two styles using StyleSheet.create below the Header class:

// src/header.js

export default class Header extends Component {
    ...
}

const style = StyleSheet.create({
    container: {
        backgroundColor: "#1BB759"
    },

    text: {
        color: "white",
        textAlign: "center",
        fontSize: 18,
        padding: 20
    }
});

If you've ever used CSS before, this should look familiar, but with two key differences. First, property names are camel-cased rather than hyphenated (ie. textAlign instead of text-align). The second and more important difference is that there are no CSS selectors here. Instead, we assign a name to each set of properties—in this case container and text.

 

So how do we actually apply the styles to our components? Components in React Native support properties that you can provide when rendering, similar to HTML attributes. One of these properties is the style property, allowing you to provide the styles to use when rendering.

Back in the render method, let's apply our styles:

render() {
    return (
        <View style={ style.container }>
            <Text style={ style.text }>React Users</Text>
        </View>
    )
}

As you can see, we're applying the container style to the outer View, and the text style to the Text component. It's worth noting that you can also define styles inline if neccessary, like so:

<View style=>
    ...
</View>

You can also apply multiple styles to a single component by passing an array of styles to the style property:

<View style={[style.style1, style.style2, style.style3]}>
    ...
</View>

For now, let's run the application and see what we've created. You should see the header at the top of the application view like so:

React Native tutorial Header Component

It may not seem like it, but we've already covered quite a bit. We've developed our first custom React Native component, imported and used it from the root application component, and styled it using a CSS-esque styling system. But, we still have a long way to go before we can call this application functional. Next up, let's add our list view with some mock data.

Creating the List View

React Native comes with a built-in ListView component that mimics the functionality you'd find in the standard Android ListView. This component uses a backing data source to display a vertically scrolling list of rows (one for each element in the data source) that the user can navigate through and interact with. Another important piece of functionality the ListView provides is an onEndReached callback function. We'll use this later on when we're using real data in order to trigger an API call to fetch the next page of users for our list, but we'll come back to that later.

For now, let's create a new source file in the src/ directory called userlistview.js, and set up the basic structure of our list. First we'll add imports for the React and React Native classes we'll be using, and define some static mock data taken from RandomUser.me. Later on we'll replace the mock data with API calls, but for now the static data will suffice. It's important to recognize that the mock data we're using has the same structure as the real data will when we get to that, which will help reduce the amount of code that needs to be changed.

// src/userlistview.js

import React, { Component } from 'react';
import {
  StyleSheet,
  ListView,
  Text,
  View,
  Image
} from 'react-native';

const data = [
    {
        name: {
            first: "romain",
            last: "hoogmoed"
        },
        picture: {
            thumbnail: "https://randomuser.me/api/portraits/thumb/men/83.jpg"
        }
    },
    {
        name: {
            first: "naomi",
            last: "fabre"
        },
        picture: {
            thumbnail: "https://randomuser.me/api/portraits/thumb/women/91.jpg"
        }
    }
]

The only new bit here is the data constant, which simply contains an array of mock users that we'll be displaying, in JSON format.

Now let's define our UserListView component below the imports and mock data:

// src/userlistview.js

export default class UserListView extends Component {
    constructor() {
        super();
        const ds = new ListView.DataSource({rowHasChanged: this._rowHasChanged});

        this.state = {
            dataSource: ds.cloneWithRows(data)
        }
    }

    render() {
        return (
            <ListView
                dataSource={this.state.dataSource}
                renderRow={this._renderRow}
                enableEmptySections={true} />
        )
    }

    _renderRow(row) {
        return (
            <View style=>
                <Image source=
                          style= />

                <Text style=>
                    {row.name.first + " " + row.name.last}
                </Text>
            </View>
        )
    }

    _rowHasChanged(r1, r2) {
        return r1 !== r2
    }
}

This is the largest class we've created so far, and there are a few new concepts and methods here so let's go over them one by one.

First we define a constructor for our component that sets the initial state. The state of a component is used to store mutable data that can be changed to update the component throughout it's lifecycle. We start by defining a new ListView.DataSource and providing it a callback for rowHasChanged, which is a required function to determine if a row needs to be re-rendered. Next we set the initial component state to an object containing a dataSource property, which is our newly created datasource cloned with our mock data.

Next we have the render function that we've used before. Here we render a ListView and set three properties: dataSource, renderRow, and enableEmptySections. dataSource is given the dataSource property of our component's state, and renderRow is given a function that takes the data of a row as input and renders it's view. This is equivalent to the getView method of an Adapter in Java. The enableEmptySections property silences a warning regarding the rendering of empty sections, which will occur later on when we don't have initial mock data to display because we'll have to wait for the network to load the initial page of users.

Next we define and implement the two functions we provided to the DataSource and ListView. _renderRow takes the data of a single row as input, and returns the view for that row to be displayed in the ListView. You'll note here that the styles are defined inline for the sake of brevity, but I'd recommend that you use a StyleSheet like we did in src/header.js. Next we render an Image component and provide it with a remote URL from our mock data row that is being passed in. The Image component in React Native will perform the necessary network request and caching to display the image for us without needing to write networking code. Finally we display a Text component containing the user's first and last name.

The final function, _rowHasChanged, was provided to our DataSource to determine if a row has changed and thus needs to be rendered again. Here we simply perform a strict equality operator using !== to determine if the row has changed - this function is required for the ListView and you'll get an exception without it. Essentially you just want to check if the data has changed, so you're own equality rules will apply based on your application use case.

You'll also notice that these two functions are prefixed with an underscore. This is used to signify that they are private functions, and as such should only be used internally by the UserListView class.

Alright, there was a lot of new ground covered here, but hopefully you're keeping up and starting to understand the React concepts.

Next let's import our new UserListView into index.android.js and place it below the Header. Note that we'll also have to add an import for the View component, and wrap the Header and UserListView in a View. This is because React Native requires that a single root component is returned from the render function, so you cannot have two top-level components as siblings within a single component. However, within the wrapping View component, we can have our header and list views as siblings:

// index.android.js

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

import Header from './src/header'
import UserListView from './src/userlistview';


export default class ReactUsers extends Component {
  render() {
    return (
        <View>
            <Header />

            <UserListView />
        </View>
    )
  }
}

AppRegistry.registerComponent('ReactUsers', () => ReactUsers);

As you can see, the Header and UserListView are siblings of one another within a wrapping View component. Now is a good chance to run the application and have a look at our new list:

React Native tutorial ListView

It looks like the application is starting to come together, but it's time to bring in some real data.

Networking

React Native comes with a networking API called Fetch, which is similar to the XMLHttpRequest (AJAX) API available on the web.

Let's first take a look at a sample of the JSON we'll be receiving from the RandomUser.me API:

{
  "results": [
    {
      "gender": "male",
      "name": {
        "title": "mr",
        "first": "romain",
        "last": "hoogmoed"
      },
      "location": {
        "street": "1861 jan pieterszoon coenstraat",
        "city": "maasdriel",
        "state": "zeeland",
        "postcode": 69217
      },
      "email": "romain.hoogmoed@example.com",
      "login": {
        "username": "lazyduck408",
        "password": "jokers",
        "salt": "UGtRFz4N",
        "md5": "6d83a8c084731ee73eb5f9398b923183",
        "sha1": "cb21097d8c430f2716538e365447910d90476f6e",
        "sha256": "5a9b09c86195b8d8b01ee219d7d9794e2abb6641a2351850c49c309f1fc204a0"
      },
      "dob": "1983-07-14 07:29:45",
      "registered": "2010-09-24 02:10:42",
      "phone": "(656)-976-4980",
      "cell": "(065)-247-9303",
      "id": {
        "name": "BSN",
        "value": "04242023"
      },
      "picture": {
        "large": "https://randomuser.me/api/portraits/men/83.jpg",
        "medium": "https://randomuser.me/api/portraits/med/men/83.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/men/83.jpg"
      },
      "nat": "NL"
    }
  ],
  "info": {
    "seed": "2da87e9305069f1d",
    "results": 1,
    "page": 1,
    "version": "1.1"
  }
}

For our purposes, we care only about the results array of user data, and not about the info object.

In order to use the API, we'll start off by creating a new source file called src/api.js. We'll use this to handle networking requests to the RandomUser.me API endpoint as the user scrolls through the UserListView. Let's create the source file and export a single method named fetchUsers:

// src/api.js

const endpoint = "https://randomuser.me/api/?results="

export default function fetchUsers(limit) {

    return fetch(endpoint + limit)
        .then(res => {
            return res.json();
        })
        .then(json => {
            return json.results;
        })
        .catch(error => {
            console.error(error);
        });

}

We start by defining a constant endpoint which is the URL prefix we'll use to fetch batches of users. Next we define and export a fetchUsers function which takes a single limit argument, indicating the number of users to return.

Within fetchUsers we use the fetch method to perform a network request to the API endpoint while also appending the limit parameter to the query string in the URL. Networking is an asynchronous operation that returns a Promise, which allows you to chain functions to run sequentially, regardless of whether each function is asynchronous or synchronous. They can take a little getting used to, but what's important is to understand that each then function will be executed in order after the previous one has completed. The catch acts similar to a try/catch in Java, where it will only be executed if a function in the promise chain fails with an error.

Our first then function simply converts the HTTP response to JSON format, and the second returns the results property of the JSON, which we saw in the JSON of the API response sample above.

You'll also notice that we return the promise chain. This allows the caller to extend the chain by adding more then and catch functions to the chain to be executed when the time is right. We'll see how this works in a moment.

Now that we've defined our API functionality, we can go ahead and get rid of the mock data in our UserListView. Remove the const data property in UserListView and replace it with an import to our API:

// src/userlistview.js

import fetchUsers from './api';

Next we'll modify our constructor to use a new _getNextPage function rather than the mock data:

// src/userlistview.js

constructor() {
    super();

    const ds = new ListView.DataSource({rowHasChanged: this._rowHasChanged});
    this.state = {
        pageSize: 10,
        data: [],
        dataSource: ds
    }

    this._getNextPage();
}

_getNextPage() {
    var self = this;

    fetchUsers(self.state.pageSize)
        .then(function(users) {
            var data = self.state.data.concat(users);

            self.setState({
                data: data,
                dataSource: self.state.dataSource.cloneWithRows(data)
            })
        });
}

We've added a couple of properties to the initial state of our component, namely data and pageSize. data is going to contain a master copy of all the user data we've loaded, allowing us to append to it each time we load a page of users. pageSize indicates the number of users to load at a time, and has been initialized to 10.

After setting the initial state, we execute a new function called _getNextPage. Here we call the fetchUsers function that was imported from the API, and we add a single callback to the promise where we concatenate the results to the data property defined in the component state. You'll note we keep a reference to this called self in order to reference "this" from within the fetchUsers callback below—this is a common requirement in JavaScript because this within a callback function references the function itself, not the outer class. This is similar to the need for ActivityName.this from within anonymous functions of an Activity in Java. Finally, we call setState on the component to update the data and dataSource properties with the newly retrieved users. This is important because the state can only be directly set within the constructor, after which you must use setState to update only the properties that have changed; for instance, we aren't updating pageSize so we don't provide it to setState.

At this point we'll load an initial batch of users when the UserListView is constructed, but we want to continue loading users as we scroll through the list. With the functionality we've currently built, we're actually almost there, we just need a small update in the render function:

render() {
    return (
        <ListView
            dataSource={this.state.dataSource}
            renderRow={this._renderRow}
            enableEmptySections={true}
            onEndReached={this._getNextPage.bind(this)}
            pageSize={this.state.pageSize} />
    )
}

We're providing two more properties to the ListView now, onEndReached and pageSize. onEndReached takes a function to be executed when the last row of the list view has been rendered, and pageSize indicates the number of rows to render at a time. Knowing that we always load state.pageSize number of users, we can improve the performance of our list view by having it render the exact same number.

One final note on the render function: If you look at how we provide _getNextPage to the onEndReached property, you'll notice that we add .bind(this) to the function. This is a pitfall of scoping in JavaScript, where if we omit the call to bind, we would be unable to use this within _getNextPage because it would refer to the ListView instance and not the UserListView instance. bind returns a copy of the function bound to a specific instance—in this case this, which refers to the UserListView. An entire book could be written on JavaScript scope, but if you're curious, try removing bind and see if you can figure out what's wrong. For more information, check out the bind documentation.

With our API defined and implemented, go ahead and run the application and watch as users are loaded. Scroll down to the bottom of the list view, and you should find that more users are loaded to allow you to keep scrolling infinitely. Note that I did find at certain times the API was rather slow, taking upwards of five seconds to respond. However, most of the time the API would response very quickly and you could smoothly scroll through the list without a hitch. In a real app you would probably want to add a loading indicator so the user is aware that more data is coming. I'll leave this as a challenge to the reader, but check out the built-in ActivityIndicator component and the renderFooter property of the ListView for a head start.

React Native tutorial UserListView

Touch Events

Next up we're going to handle touch events on the rows of the list view. When a row is selected, we'll open a modal displaying the full details of the selected user.

The first thing we'll want to do is wrap our rendered rows in a TouchableHighlight component, which is a built-in component for highlighting the selected row. This is consistent with the majority of list UIs where a highlight is added to the touched row to give feedback to the user.

Let's add TouchableHighlight to our imports from react-native:

// src/userlistview.js

import {
  StyleSheet,
  ListView,
  Text,
  View,
  Image,
  TouchableHighlight
} from 'react-native';

Next let's update _renderRow to wrap it's current content with a TouchableHighlight:

// src/userlistview.js

_renderRow(row) {
    return (
        <TouchableHighlight activeOpacity={80}
                    underlayColor={"#1BB759"}>
            <View style=>
                <Image source=
                       style= />

                <Text style=>
                    {row.name.first + " " + row.name.last}
                </Text>
            </View>
        </TouchableHighlight>
    )
}

We're also setting two properties on the TouchableHighlight to dictate the highlight style when a row is touched. The activeOpacity is the opacity of the row when it is touched, and underlayColor is the color set behind the row, so that when it is touched and opaque, the underlayColor comes through.

If you run the application now, you'll find that nothing happens when you touch a row. The reason for this is that the underlying ListView has no idea that we've modified the view of a row, and so we need to inform it. Because highlighting a selected row is such a common task, React Native actually provides a rather unique means of informing the list view that a highlight has occurred.

Up until now we've accepted only one parameter to the _renderRow function, which is the data to be displayed (i.e. row). There are actually three more parameters that the ListView has been providing us however, and we've simply been ignoring them. The first two are section and row identifiers that give us a means of referencing individual rows, and the final parameter is a function that can be called when a row is being highlighted to inform the ListView that it needs to be redrawn.

Let's update our _renderRow method to accept these parameters:

// src/userlistview.js

_renderRow(row, sectionId, rowId, highlightRow) {
    ...
}

And now we can listen for the onPress event of our TouchableHighlight, and call the highlightRow function with the section and row identifiers:

// src/userlistview.js

_renderRow(row, sectionId, rowId, highlightRow) {
    return (
        <TouchableHighlight activeOpacity={80}
                            underlayColor={"#1BB759"}
                            onPress={function() {
                                highlightRow(sectionId, rowId)
                            }}>

        ...

        </TouchableHighlight>
    )
}

Run the application now and you should see a nice green highlight to match our header when you tap a row.

React Native tutorial TouchableHighlight

Now that the user has some feedback for the row they're selecting, let's get to work on displaying the full user details when a row is selected. We'll create a new class to be displayed in a React Native Modal by the UserListView when a user is selected, and we'll provide the new class with the details of the user using custom properties.

Before we create our modal however, we'll want to have a way to bubble up the user selection event so that the UserListView will not be responsible for displaying the user details modal. In order to do this, we'll create a custom property on the UserListView class called onUserSelected. This property will accept a function that receives the user details, and can do with it what it likes—in this case, display the user details modal. Custom properties work exactly the same as the properties we've used so far, such as onPress on the TouchableHighlight component.

Component properties in React Native are accessed using the props property of the component class, very similar to how we use state. In our TouchableHighlight onPress callback function, we'll call our new custom onUserSelected property and provide the user data:

// src/userlistview.js

_renderRow(row, sectionId, rowId, highlightRow) {
    var self = this;

    return (
        <TouchableHighlight activeOpacity={80}
                            underlayColor={"#1BB759"}
                            onPress={function() {
                                highlightRow(sectionId, rowId)
                                self.props.onUserSelected(row)
                            }}>
        ...
    )
}

Two things have changed here: The first is we're keeping a reference to this outside the callback function called self, and the second is that we're using it to reference the component's onUserSelected property via self.props.onUserSelected in the onPress callback. We execute the onUserSelected function and provide it with our row, which contains the user details as retrieved from the remote API.

One more change is required however, and that is the bind the call to _renderRow from within render. We discussed bind and why it's necessary above, and now that we need reference to this, (the UserListView instance) we'll require a bind. Scope has never been JavaScript's strong suit.

// src/userlistview.js

render() {
    return (
        <ListView
                ...
                renderRow={this._renderRow.bind(this)}
                ...
  )
}

Alright, now that we've got a custom property, it's time to make use of it. We're going to go back to index.android.js where we render the UserListView and supply the onUserSelected prop.

// index.android.js

render() {
    return (
        ...
        <UserListView onUserSelected={this._onUserSelected.bind(this)}/>
        ...
    )
}

_onUserSelected(user) {
    alert("Selected User:\n" + JSON.stringify(user))
}

For now we're simply going to alert the user JSON in order to ensure we've got everything hooked up correctly. If you run the application and select a user, you should see something like this:

React Native tutorial Alert Dialog

Isn't that beautiful? Looks like a native Dialog, doesn't it? That's because the standard JavaScript alert method has been replaced with a native Dialog on Android and a UIAlertView on iOS. Now that we've got this beautifully presented dialog, it almost doesn't seem necessary to create a custom modal. Okay, maybe we need a better interface for our users.

The first thing we'll do is create out UserDetailsView class in src/userdetailsview.js:

// src/userdetailsview.js
import React, { Component } from 'react';
import {
  Text,
  View,
  Image,
  TouchableHighlight
} from 'react-native';

export default class UserDetailsView extends Component {

    render() {
        var user = this.props.user;

        return (
            <View>
                <Image source=
                            style= />

                <View style=>
                    <Text style=>
                        {user.name.first + " " + user.name.last}
                    </Text>
                    <Text>{user.login.username}</Text>
                    <Text>{user.email}</Text>
                    <Text>{user.cell}</Text>

                    <TouchableHighlight style=
                                        onPress={this._close.bind(this)}>
                        <Text style=>
                            OK
                        </Text>
                    </TouchableHighlight>
                </View>
            </View>
        )
    }

    _close() {
        this.props.onClose();
    }

}

There's shouldn't be anything unfamiliar in this class, but let's walk through it step by step. First we have our imports and class definition—in this case our class is UserDetailsView. Within the class we have our render function, where we render the user's profile picture followed by their name, username, e-mail, and cell phone number. We use a custom user property, so we'll have to remember to provide that when rendering the UserDetailsView.

Below the user details we have a button, using the TouchableHighlight component again, and we provide a _close function to the onPress property. The _close function then calls an onClose property on the UserDetailsView component, so this should tell you that we're going to need to provide an onClose function when we render this component. At this point you should be starting to be able to picture the UI of this component based on the source code, but just to make sure, we're going to display it so we can actually see how it looks.

Back in index.android.js we're going to modify our onUserSelected function to actually display the UserDetailsView in a Modal.

// index.android.js
...
import {
  View,
  AppRegistry,
  Modal
} from 'react-native';
...
import UserDetailsView from './src/userdetailsview';

export default class ReactUsers extends Component {

  constructor() {
    super();
    this.state = {
      user: null,
      userDetailsVisible: false
    }
  }

  render() {
    var self = this;

    return (
        <View>
            <Header />

            <UserListView onUserSelected={this._onUserSelected.bind(this)} />

            <Modal animationType={"slide"}
                   visible={this.state.userDetailsVisible}
                   onRequestClose={function() {}}>

                <UserDetailsView user={this.state.user}
                                    onClose={this._onCloseUserDetails.bind(this)} />

            </Modal>
        </View>
    )
  }

  _onUserSelected(user) {
    this.setState({
      userDetailsVisible: true,
      user: user
    })
  }

  _onCloseUserDetails() {
    this.setState({
      userDetailsVisible: false,
      user: null
    })
  }
}

Alright, so a few changes here. First we have our import statements, where we've added the Modal and UserDetailsView components. Considering you're practically a React Native expert by now, this should come as no surprise.

Next we've added a constructor to set the initial state of the component. We're going to be referencing a user and userDetailsVisible property in our state, with user being the selected user object, and userDetailsVisible being a boolean to determine if we should render the UserDetailsView modal.

Next up we have the render function, where we've added a Modal and a UserDetailsView as its child. The Modal is receiving three properties, the first of which is an animationType (check the link for possible animations), and the second is a visible boolean. When this boolean is set to true, the Modal will animate into view, and when it's set to false, it will animate out of view. The final property, onRequestClose, is a required property on Android devices only. The fact that it's required only on Android is quite strange as we don't actually have to do anything in the function, but regardless, it's called when the Modal is going to be dismissed. UserDetailsView receives two properties: the user to display, and an onClose callback function which we'll see in a moment.

The next change here is that we've updated _onUserSelected to keep a reference to the user in the component state, along will setting userDetailsVisible to true. Finally, we've added an _onCloseUserDetails function which was passed into UserDetailsView as the onClose callback. Here we reset the state to not reference the displayed user, and to set the userDetailsVisible toggle to false.

Let's go ahead and run the application, and after selecting a user you should see the following modal slide into view:

React Native tutorial custom Modal

Tapping the OK button at the bottom should then close the modal, allowing you to select another user. The close button works well enough, but try the native back button on your Android device... Nothing happens.

Remember when I mentioned that strange onRequestClose property of the Modal component, and how it's only required for Android? What's one thing Android devices usually have that iOS devices don't? A back button. It makes sense that when the back button is pressed, the Modal might request to close, and thus call a function called onRequestClose, so let's try providing our _onCloseUserDetails function and see what happens.

// index.android.js

...
<Modal animationType={"slide"}
       visible={this.state.userDetailsVisible}
       onRequestClose={this._onCloseUserDetails.bind(this)}>
...

Give that a run and what do you know: It works! This works great for modals where the functionality is built into the component, but if you ever need to provide your own support for the Android back button, there is also a BackAndroid class to add listeners to the hardware button. It's worth mentioning that there are actually a number of native Android (and iOS) bridges built into React Native, so it's certainly worth your while to check out the component and API list on the React Native website.

Tools

At this point we've got ourselves a fully functional application that presents a list of data, fetched over the network along with remote images, and displays stateful UIs to present user details. That's not too bad for a handful of classes and about 250 lines of code isn't it? Not to mention the fact that it's already ready to go on two platforms! I'd say this is pretty powerful for prototyping and even for full-fledged production applications.

But we're not done just yet because React Native also provides us with a number of powerful tools to help build and debug our applications. You may have noticed some prompts telling you to shake your device, so go ahead and do that now. You should be presented with a dialog that looks like so:

React Native tutorial Developer Tools

There are a number of tools here and an entire article could be written about them, but I'll go over what I think are the most useful ones when you're getting started with React Native.

The first is the "Reload" button, which requests the latest source code and refreshes the application. This is useful because it saves the time required to bundle and install the full APK. Similarly the "Hot Reload" and "Live Reload" options can be toggled to automatically reload the source code and refresh the application whenever source code changes. You may be annoyed that I didn't mention these at the start of the article, but I've found they're a little buggy and inconsistent at times, and for a beginning React Native developer they can actually cause more harm than good when things aren't acting quite as you expect. Occasionally you'll need to "hard refresh" the application by building and installing using the react-native run-android command, but overall these can be a time saver when they are cooperating. I'm sure over time the React team is going to greatly improve these features, but for now I don't recommend them when you're first starting out with React Native. After completing this tutorial however, you should know what to expect when developing with React Native so feel free to use the tools.

Next we have what I find is the most helpful tool for debugging, which is the "Debug JS Remotely" button. This opens a new tab in Google Chrome where you can open the developer tools (right click -> Inspect) to see the console output of your application. This will display messages printed with console.log as well as any exceptions and accompanying stack traces.

React Native tutorial Google Chrome Debug Console

Similarly, you may have noticed the red (error) and orange (warning) messages at the bottom of your Android screen if you made any mistakes while following this article. Clicking those will give you a decent stack trace and message in-app while developing, which can come in handy.

The last tool I'm going to cover is the inspector, accessed by the aptly named "Inspect" button. This allows you to dig into the details of your view, including style properties like margins and padding, as well as the view heirarchy. This can be extremely helpful when building complex UIs and when things aren't displaying quite how you'd like them. The tool works very similarly to the Google Chrome inspector, so web developers will feel right at home here.

React Native tutorial Inspector

Wrapping Up

As I mentioned in the beginning, all the source code for this tutorial is available on GitHub, so feel free to take a look if anything was unclear. For more on Android development, including Java, Unity, and React Native, feel free to follow me and reach out on Twitter @kylewbanks or check out my blog at kylewbanks.com.

Hopefully this tutorial has served to give you an understanding of how React Native works, and how productive you can be with this framework!

Related Search Term(s): React Native

Create, Design, Develop and Connect at AnDevCon D.C. 2017!

Thoughts? Leave a comment: