Ever wanted to build an iOS or Android app? But maybe you don’t want to learn Swift or use an HTML5 wrapper? Wouldn’t it be great if you could build fast native mobile apps in the same language you write web apps in (JavaScript)? Well your prayers have been answered, with React Native, thanks to the wonderful web wizards over at Facebook.

Today we’re going to be building a coffee finder app called WheresMyCoffee. It will be an iOS app that uses the Yelp API to find nearby coffee places (perfect for coffee addicts, which is nearly everyone!).

This tutorial is won’t be super advanced but it won’t stop to define the basics of React. This is a good intro tutorial for newbies to React.

Explore React Courses

This is what your finished app will look like:

You can find the source code for this project here: https://github.com/stefanlenoach/WheresMyCoffee

The following is an outline of how we’re going to tackle this:

Setup: Self-explanatory Hello World: Getting your first React Native app up and running Fetching the Data: Using the Yelp API to fetch the data we want Navigator: Setting up the React Navigator so we can access new views Displaying our Data: Pulling it all together and finishing the app Bonus: Moving forward

Part 1: Setup

If you don’t already have Xcode, download it from the App Store

Next, we’re going install Node.js and Watchman with Homebrew. Enter the following commands in an open terminal session:

brew install node brew install watchman

Watchman is a tool by Facebook for watching changes in the filesystem. It is highly recommended you install it for better performance

Once you have Node and Watchman installed, we can install React Native with npm:

sudo npm install -g react-native-cli

Congratulations, you successfully installed React Native! Let’s test it out with a simple Hello World app.

Part 2: Hello World

We’ll use the React Native command line tools to create our app.

Navigate to your desktop and run the following commands in a new terminal:

react-native init WhereMyCoffeeAt cd WhereMyCoffeeAt react-native run-ios

If everything worked you should have a couple of new screens pop up. The first is the React Packager, which basically functions like webpack. *Leave this up in the background while you’re working on your app. The second screen should be the iOS Simulator. It might take a moment to load, but you should end up with a screen looking something like this:

Now, open up the project in your favorite text editor and pull up index.ios.js. Let’s break down what’s going on here.

import React, { Component } from 'react'; import { AppRegistry } from 'react-native' import AppNavigator from './app/navigation/AppNavigator' class WheresMyCoffee extends Component { render() { return ( <AppNavigator initialRoute={{ident: "Search"}} /> ) } } AppRegistry.registerComponent('WheresMyCoffee', () => WheresMyCoffee)

At the top of the file we import React and a bunch of React Native components. Below that, we see the root component of our application. Anyone familiar with React will immediately understand what’s going on here. React Native components are written in .jsx and have the same lifecycle normal React components use. But you may be wondering, what are these weird <View> and <Text> tags? And where are my divs? In React Native you won’t have access to the HTML tags you’re used to. Instead, we have to import components from the React Native library. For now, just know that <View> tags take the place of <div> tags and <Text> tags replace <span> or <p> tags. You can read more on this in the Facebook docs.

Underneath our component, we see a StyleSheet object containing all our styling information. This is another idiosyncrasy of React Native, we define all our styling inline instead of abstracting it out to .css files.

Change the background color to ‘#eee’, save, and in your iOS Simulator hit Command⌘ + R. The reload times are impressive for mobile app building. Now, delete all the <Text> tags and a new one with the title of our app. Your render function should look like this:

render() { return ( <View style={styles.container}> <Text style={styles.welcome}> WheresMyCoffee </Text> </View> ); }

As you can see, we define the style of an element via the ‘style’ attribute. In our case, we pass in our styles object which is defined right under our render function. Equivalently, we could define the style attribute directly like this:

<Text style={{fontSize: 30, textAlign:'center'}}>

In the next section, we’re going to build ourselves a button that retrieves data from Yelp’s API.

Part 3: Fetching The Data

We want to be able to click a button on our home screen and have it fetch nearby cafes. How do we do this? The first thing we’ll need is our location in terms of latitude and longitude. Luckily for us, we can use JavaScript’s Navigator object to do this relatively painlessly. Let’s define our state and componentDidMount functions so that our app automatically gets this data when it loads the home screen. Our state will simply define our position as ‘unknown’ to start with:

state = { position: 'unknown' }; Now, in componentDidMount, we can use navigator.geolocation.getCurrentPosition to get our position in terms of latitude and longitude, then set our state: componentDidMount() { navigator.geolocation.getCurrentPosition( (position) => { this.setState({position}); }, (error) => alert(error), {enableHighAccuracy: true, timeout: 20000, maximumAge: 1000} ); }

Great! So now our ‘position’ state should hold an object with our current latitude and longitude. How can we check this to make sure? If you click on the iOS simulator and hit Command⌘ + D, you’ll see the developer menu pop up. Click on ‘Debug JS Remotely’. A new window should open in your browser with the JavaScript console open. Now, you can add debuggers and std.out messages to your app the same way you would if you were developing for the web. Insert a debugger at the end of componentDidMount and make sure that your state is what you expect it to be.

Now, we want to use our latitude and longitude in our Yelp API request to find local cafes. Let’s create a button that will do this when we press it. First, add TouchableOpacity to your list of imports from ‘react-native’:

import {StyleSheet, Text, View, TouchableOpacity} from ‘react-native’;

This is the component we’re going to use to build our button. Next, we’ll add this to our view container and set the onPress attribute to call ‘fetchData’, our function that will make our API request. The onPress attribute is essentially the React Native analogue to onClick. Your render function should look something like this:

render() { return ( <View style={styles.container}> <Text style={styles.welcome}> WheresMyCoffee </Text> <TouchableOpacity style={{borderRadius: 7,padding: 10, backgroundColor: 'rgb(37, 160, 205)'}} onPress={this.fetchData.bind(this)}> <Text style={{fontSize: 15}}>Find Coffee!</Text> </TouchableOpacity> </View> ); }

As you can see, I’ve added .bind(this) to our fetchData method so that we’ll have access to the component state. I’ve also added another Text wrapper and some styling so that our button looks decent and gives the user a call to action. Now, define the fetchData method in your component and leave it empty. Reload your app and make sure everything is working properly.

We’re finally ready to start working with the Yelp API. The first thing you’ll need to do is set up a Yelp account and create a new app from the developers page

Explore React Courses

Once you create your app, Yelp will provide you with a Consumer Key, Consumer Secret, Token, and Token Secret. Now go back to your terminal and install and save OAuthSimple:

npm install –save oauthsimple

OAuthSimple will help us easily build our fetch call which we can then use to grab the data we want. Import OAuthSimple at the top of your file:

import OAuthSimple from 'oauthsimple'

You will have to define the consumerKey, consumerSecret, token, and tokenSecret somewhere safe in your environment. DO NOT post these keys publicly. Your OAuthSimple implementation should look something like this:

var lat = this.state.position.coords.latitude var lng = this.state.position.coords.longitude var latlng = "ll=" + String(lat) + "," + String(lng) var oauth = new OAuthSimple(consumerKey, tokenSecret) var request = oauth.sign({ action: "GET", path: "https://api.yelp.com/v2/search", parameters: "term=coffee&" + latlng, signatures: {api_key: consumerKey, shared_secret: consumerSecret, access_token: token, access_secret: tokenSecret}, }) Using oauth.sign creates an object with the signed URL already inside. All we have to do now is make a ‘GET’ request to this address: fetch(request.signed_url, {method: "GET"}).then(function(response){ return response.json() }).then(function(data){ debugger }).catch(function(error){ console.log("Error:", error) })

Reload your app and look in your developer console. If everything worked, you should be able to examine the ‘data’ object and see it has all the information we need inside of the ‘businesses’ attribute. Not too bad, right? So now that we have our data, all that’s left for us to do is present it in a way that makes sense. In the next section, we’ll restructure our program around React Native’s Navigator so we can present the user with a new screen showing our results after they press our button.

Part 4: Navigator

React Native’s Navigator handles the transition to different scenes/screens in your application. You can think of it kind of like the React Router of mobile app development. We’re going to structure our app to revolve around the navigator so we can effortlessly move to new scenes whenever we want. First, lets create a new folder in our main directory called ‘app’. This will contain our app components and navigator which we will access from index.ios.js. Inside of your ‘app’ directory create two more folders: ‘components’ and ‘navigator’.

Let’s first create a new file in our ‘components’ folder, call it ‘Search.js’, and move everything we’ve written so far into it. Delete everything related to the AppRegistry, change the component name from WheresMyCoffee to Search, and lastly export the component at the end of the file. Here’s what search.js will look like:

import React, { Component } from 'react'; import {StyleSheet, Text, View, TouchableOpacity} from 'react-native'; import OAuthSimple from 'oauthsimple' class Search extends Component { state = { position: 'unknown' }; componentDidMount() { navigator.geolocation.getCurrentPosition( (position) => { this.setState({position}); }, (error) => alert(error), {enableHighAccuracy: true, timeout: 20000, maximumAge: 1000} ); } fetchData() { var lat = this.state.position.coords.latitude var lng = this.state.position.coords.longitude var latlng = "ll=" + String(lat) + "," + String(lng) var consumerKey = "***" var consumerSecret = "***" var tokenSecret = "***" var token = "***" oauth = new OAuthSimple(consumerKey, tokenSecret) request = oauth.sign({ action: "GET", path: "https://api.yelp.com/v2/search", parameters: "term=coffee&" + latlng, signatures: {api_key: consumerKey, shared_secret: consumerSecret, access_token: token, access_secret: tokenSecret}, }) var nav = this.props.navigator fetch(request.signed_url, {method: "GET"}).then(function(response){ return response.json() }).then(function(data){ nav.push({ ident: "Results", data: data }) }).catch(function(error){ console.log("Error:", error) }) } render() { return ( <View style={styles.container}> <Text style={styles.welcome}> WheresMyCoffee </Text> <TouchableOpacity style={{borderRadius: 7,padding: 10, backgroundColor: '#4d9be3'}} onPress={this.fetchData.bind(this)}> <Text style={{fontSize: 15}}>Find Coffee!</Text> </TouchableOpacity> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#fff', }, welcome: { fontSize: 30, textAlign: 'center', margin: 10, marginBottom: 30 } }); module.exports = Search

Now, create a new file, ‘AppNavigator.js’ in our ‘navigator’ directory. Let’s first import everything we’ll need to build our component and define our component which we’ll aptly name ‘AppNavigator’:

import React, { Component } from 'react'; import { Navigator } from 'react-native' import Search from '../components/Search' class AppNavigator extends Component { render() { return ( ) } } module.exports = AppNavigator

In our render method we’ll simply return the Navigator component we imported from the React-Native library:

<Navigator initialRoute={this.props.initialRoute} renderScene={this._renderScene} configureScene={(route) => Navigator.SceneConfigs.FloatFromRight } />

The initialRoute attribute, as you might have guessed, defines the first scene that the navigator will render. In this example, we will pass down our initial route as props from index.ios.js. The renderScene attribute will take a method that will act as a giant switch that will give us the ability to tell the navigator what to show the user based on the route that is passed to it. Lastly, the configureScene attribute will tell the Navigator how we want to visualize the change in scene. Let us now define renderScene:

renderScene(route, navigator) { var globalNavigatorProps = { navigator } switch(route.ident) { case "Search": return ( <Search {...globalNavigatorProps} /> ) } }

renderScene takes two arguments: the route, which will determine which scene is rendered, and the navigator, which is a reference to itself. We define the globalNavigatorProps so that we can access the navigator from any scene. Finally, we create our switch which will render different scenes depending on the route we decide to pass in. Before we reload our app, let’s rewrite our index.ios.js file so that we render our AppNavigator:

import React, { Component } from 'react'; import { AppRegistry } from 'react-native' import AppNavigator from './app/navigation/AppNavigator' class WheresMyCoffee extends Component { render() { return ( <AppNavigator initialRoute={{ident: "Search"}} /> ) } } AppRegistry.registerComponent('WheresMyCoffee', () => WheresMyCoffee)

Reload your app and make sure that everything is working the way it was before we restructured our app. We’re almost done!

Part 5: Displaying Our Data

Create a new file in our components folder named ‘Results.js’. Back in our AppNavigator.js file, we’re going to add another case statement which we will navigate to once our data has been retrieved (don’t forget to import our Results component!):

case "Results": return ( <Result {...globalNavigatorProps} data = {route.data} /> )

Now, find your way back to Search.js. Inside our fetchData method we want to push our Results scene onto the stack, passing in the data we retrieved as props. We have access to the navigator already since we passed it in as props earlier. Your API request will look like this now:

var nav = this.props.navigator fetch(request.signed_url, {method: "GET"}).then(function(response){ return response.json() }).then(function(data){ nav.push({ ident: "Results", data: data }) }).catch(function(error){ console.log("Error:", error) }) Notice that once we have our data, we push the “Results” route onto the stack with our data object. We’re finally ready to start fleshing out our Results page.

Explore React Courses

In Results.js, let’s first flesh out the skeleton of our component: import React, { Component } from ‘react’; import { StyleSheet, Text, View, Image, ListView, Linking, TouchableOpacity} from ‘react-native’; class Results extends Component { constructor(props) { } render() { return ( ); } } const styles = StyleSheet.create({ }); module.exports = Results

We’re going to display our list of items by using React Native’s ListView component. I highly recommend going through the documentation for this one since the way lists are rendered in React Native is actually quite different than the way you would for React on web apps. Before we can use the ListView we have to set up a data source, which we’ll define in our constructor function:

constructor(props) { super(props) var dataSource = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 != r2}) this.state = { results: dataSource.cloneWithRows(props.data.businesses) } }

Our state now holds a list of business objects. Let’s go back to our render function and write out our ListView component:

render() { return ( <View style={styles.container}> <ListView style={{marginTop: 100}} initialListSize={10} dataSource={this.state.results} renderRow={(result) => { return this.renderResult(result) }} /> </View> ); }

The renderRow attribute will display each item in the data source. We specify how the list item will be visualized in renderResult:

renderResult(result) { return ( <TouchableOpacity style={styles.resultRow} onPress={() => Linking.openURL(result.url)}> <Image source={{uri: result.image_url}} style={{width: 80, height: 80, justifyContent: 'flex-start'}} /> <View style={{flexDirection: 'column', justifyContent: 'center'}}> <Text style={{fontWeight: 'bold'}}>{`${result.name}`}</Text> <Text>Rating: {`${result.rating}`}</Text> <Text>Phone: {`${result.display_phone}`}</Text> </View> </TouchableOpacity> ) }

The renderResult method takes in a list item (in our case, a coffee shop object) and we use various attributes on this item to create a list that makes sense. We display the coffee shop image, name, rating, and phone number. Pressing on any of the individual items will take you to that coffee shop’s Yelp page. With some final touches to make our results page look a bit nicer, your Results.js file should look like this:

import React, { Component } from 'react'; import { StyleSheet, Text, View, Image, ListView, Linking, TouchableOpacity} from 'react-native'; class Results extends Component { constructor(props) { super(props) var dataStore = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 != r2}) this.state = { results: dataStore.cloneWithRows(props.data.businesses) } } render() { return ( <View> <Text style= {styles.header}>Results</Text> <ListView style={{marginTop: 100}} initialListSize={10} dataSource={this.state.results} renderRow={(result) => { return this.renderResult(result) }} /> </View> ); } renderResult(result) { return ( <TouchableOpacity style={styles.resultRow} onPress={() => Linking.openURL(result.url)}> <Image source={{uri: result.image_url}} style={{width: 80, height: 80, justifyContent: 'flex-start'}} /> <View style={{flexDirection: 'column', justifyContent: 'center'}}> <Text style={{fontWeight: 'bold'}}>{`${result.name}`}</Text> <Text>Rating: {`${result.rating}`}</Text> <Text>Phone: {`${result.display_phone}`}</Text> </View> </TouchableOpacity> ) } } const styles = StyleSheet.create({ header:{ textAlign: 'center', position: 'relative', top: 60, fontSize: 30 }, resultRow: { flexDirection: 'row', justifyContent: 'space-around', alignItems: 'flex-start', marginBottom: 20, padding: 5, } }); module.exports = Results

Explore React Courses

Hit Command⌘ + R one more time and make sure nothing blows up. Your app should look like this:

Congratulations, we’re done! Hopefully you’ve realized by now that building iOS and Android apps is entirely accessible to anyone that knows React. If you’re stuck on any bugs, feel free to check out the source code.

Part 6: Bonus

If you want to take this app to the next level, I challenge you to try it on your own. Some modifications that could be interesting:

Sort search results by distance so that closest coffee shops appear first