React is an open-sourced JavaScript library made by Facebook for building UI applications. The project has a strong emphasis on the component based approach and utilizes full power of JavaScript for constructing all elements. React Native project was introduced on the first React conference in January 2015. It allows you to build native mobile applications using the same concepts from React. In this tutorial I am going to explain the main building blocks of React Native by the example of iOS demo application. I assume that readers have previous experience in writing web applications with React.

Setup

Please go through getting started section on React Native website if you would like to build an application on your machine.

Quick start

When all necessary tools are installed let’s initialize new React application with command:

react-native init LastFmTopArtists

After the command fetches code and dependencies you can open new project (LastFmTopArtists/LastFmTopArtists.xcodeproj) in Xcode. Then build and run the app with cmd+R. You will see a similar screen on iOS simulator:

First start

You can make changes in index.ios.js, then press cmd+R and see instant changes in the simulator.

Demo app

In this tutorial I will show you how to build a list of popular artists using Last.fm api. We will display them with help of ListView component and redirect on artist page using WebView.

First screen

Lets start with adding a new screen into our application. For now it will contain dump text. Create fileArtistListScreen with the following code:

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

var {
  ListView,
  StyleSheet,
  Text,
  View,
} = React;

class ArtistListScreen extends React.Component {
  render() {
    return (
      <View style={styles.container}>
      <Text>Artist list would be here</Text>
      </View>
    );
  }
}

var styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
    marginTop: 64
  }
})

module.exports = ArtistListScreen;

Here are some things to note:

  • I declare react components with ES6 Classes syntax.
  • ES6 Destructuring assignment syntax is used for React objects declaration.
  • FlexBox is a default layout system in React Native. Flex values can be either integers or doubles, indicating the relative size of the box. So, when you have multiple elements they will fill the relative proportion of the view based on their flex value.
  • ListView is declared but will be used later.

From index.ios.js we call ArtistListScreen using NavigatorIOS component:

var React = require('react-native');
var ArtistListScreen = require('./ArtistListScreen');

var {
  AppRegistry,
  NavigatorIOS,
  StyleSheet
} = React;

var LastFmArtists = React.createClass({
  render: function() {
    return (
     <NavigatorIOS
      style={styles.container}
      initialRoute={{
        title: "last.fm Top Artists",
        component: ArtistListScreen
      }}
      />
    );
  }
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
  },
});

Switch to iOS Simulator, refresh with cmd+R and you will see:

Empty artists list

ListView

After we have got empty screen, let’s render some mock data in a ListView component. This component has a number of performance improvements such as rendering of only visible elements and removing which are off screen. The new version of ArtistListScreen looks like the following:

class ArtistListScreen extends React.Component {
  constructor(props) {
      super(props)
      this.state = {
        isLoading: false,
        dataSource: new ListView.DataSource({
          rowHasChanged: (row1, row2) => row1 !== row2
        })
      }
    }

  componentDidMount() {
    this.loadArtists();
  }

  loadArtists() {
    this.setState({
      dataSource: this.getDataSource([{name: 'Muse'}, {name: 'Radiohead'}])
    })
  }

  getDataSource(artists: Array<any>): ListView.DataSource {
    return this.state.dataSource.cloneWithRows(artists);
  }

  renderRow(artist) {
    return (
      <Text>{artist.name}</Text>
    );
  }

  render() {
    return (
      <View style={styles.container}>
        <ListView
          dataSource={this.state.dataSource}
          renderRow={this.renderRow.bind(this)}
          automaticallyAdjustContentInsets={false}
        />
      </View>
    );
  }
}

Side notes:

  • The DataSource is an interface that ListView is using to determine which rows have changed over the course of updates.
  • ES6 constructor is an analog of getInitialState

The end result of the changes:

Screen with mock data

Api token

Last.fm web api free to use but you will need personal api token in order to access it. At first it is necessary to join Last.fm and then get an API account.

Fetching real data

Assume you have successfully setup API account. Let’s call a real web service using fetch API:

const API_KEY='put token here';
const API_URL = 'http://ws.audioscrobbler.com/2.0/?method=geo.gettopartists&country=ukraine&format=json&limit=40';
const REQUEST_URL = API_URL + '&api_key=' + API_KEY;

loadArtists() {
      this.setState({
        isLoading: true
      });

      fetch(REQUEST_URL)
        .then((response) => response.json())
        .catch((error) => {
          console.error(error);
        })
        .then((responseData) => {
          this.setState({
            isLoading: false,
            dataSource: this.getDataSource(responseData.topartists.artist)
          })
        })
        .done();
    }

After refresh the iOS simulator should display:

Screen with real data

ArtistCell

Since we have real data, it is time to add artist’s images and rank them on the display. Let’s move artist cell display logic into separate component ArtistCell:

'use strict';

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

var {
  Image,
  View,
  Text,
  TouchableHighlight,
  StyleSheet
} = React;

class ArtistCell extends React.Component {
  render() {
    return (
      <View>
        <View style={styles.container}>
          <Image 
            source={{uri: this.props.artist.image[2]["#text"]}}
            style={styles.artistImage}
          />
          <View style={styles.rightContainer}>
            <Text style={styles.rank}>## {this.props.artist["@attr"].rank}</Text>
            <Text style={styles.name}>{this.props.artist.name}</Text>
          </View>
        </View>
        <View style={styles.separator}/>
      </View>
    );
  }
}

var styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    padding: 5
  },
  artistImage: {
    height: 84,
    width: 126,
    marginRight: 10
  },
  rightContainer: {
    flex: 1
  },
  name: {
    textAlign: 'center',
    fontSize: 14,
    color: '#999999'
  },
  rank: {
    textAlign: 'center',
    marginBottom: 2,
    fontWeight: '500',
    fontSize: 16
  },
  separator: {
    height: 1,
    backgroundColor: '#E3E3E3',
    flex: 1
  }
})

module.exports = ArtistCell;

Changes in ArtistListScreen:

// declare new component
var ArtistCell = require('./ArtistCell');

// use it in renderRow method:
  renderRow(artist) {
    return (
        <ArtistCell 
          artist={artist} 
        />
      );
  }

Press cmd+R in iOS Simulator:

WebView

The last piece of the application would be to open a web page by clicking in ListView. Declare new component WebView:

'use strict';

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

var {
  View,
  WebView,
  StyleSheet
} = React;

class Web extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <WebView url={this.props.url}/>
      </View>
    );
  }
}

var styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F6F6EF',
    flexDirection: 'column',
  },
});

Web.propTypes = {
  url: React.PropTypes.string.isRequired
};

module.exports = Web;

Then by using TouchableHighlight we will call onOpenPage from ArtistCell:

class ArtistCell extends React.Component {
  render() {
    return (
      <View>
       <TouchableHighlight
          onPress={this.props.onOpenPage}
          underlayColor='transparent'>
          <View style={styles.container}>
            <Image 
              source={{uri: this.props.artist.image[2]["#text"]}}
              style={styles.artistImage}
            />
            <View style={styles.rightContainer}>
              <Text style={styles.rank}>## {this.props.artist["@attr"].rank}</Text>
              <Text style={styles.name}>{this.props.artist.name}</Text>
            </View>
          </View>
        </TouchableHighlight>
        <View style={styles.separator}/>
      </View>
    );
  }
}

Finally open web page from ArtistListScreen component:

// declare new component
var WebView = require('WebView');


class ArtistListScreen extends React.Component {
  // will be called on touch from ArtistCell
  openPage(url) {
    this.props.navigator.push({
      title: 'Web View',
      component: WebView,
      passProps: {url}
    });
  }

  renderRow(artist) {
    return (
        <ArtistCell 
          artist={artist}
          // specify artist's url on render
          onOpenPage={this.openPage.bind(this, artist.url)}
        />
      );
  }

}

Now a touch on any cell in ListView will load a web page for selected artist:

Conclusion

You can explore source code of the app on Github repo. For me it was a real fun to play with React Native. I found debugging in Chrome and error stack messages extremely easy to work with. By using React’s component-based approach you can build complex UI without much effort. I highly recommend to explore this technology for rapid prototyping and maybe for your next awesome project.