React Native Tutorial: Building iOS Apps with JavaScript
In this React Native tutorial you’ll learn how to build native iOS and Android apps based on the hugely popular React JavaScript library. By Christine Abernathy.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
React Native Tutorial: Building iOS Apps with JavaScript
40 mins
Initiating a Search
First, remove the logging code you just added above, since it’s no longer necessary.
In order to implement the search functionality you need to handle the Go button press, create a suitable API request, and provide a visual indication that a query is in progress.
Within SearchPage.js, update the initial state within the constructor:
this.state = {
searchString: 'london',
isLoading: false,
};
The new isLoading
property will keep track of whether a query is in progress.
Add the following logic to the start of render
:
const spinner = this.state.isLoading ?
<ActivityIndicator size='large'/> : null;
This is a ternary if
statement that optionally adds an activity indicator, depending on the component’s isLoading
state. Because the entire component is rendered each time, you are free to mix JSX and JavaScript logic.
Within the JSX that defines the search UI in return
, add the following line below the Image
to place the spinner:
{spinner}
Next, add the following methods to the SearchPage
class:
_executeQuery = (query) => {
console.log(query);
this.setState({ isLoading: true });
};
_onSearchPressed = () => {
const query = urlForQueryAndPage('place_name', this.state.searchString, 1);
this._executeQuery(query);
};
_executeQuery()
will eventually run the query, but for now it simply logs a message to the console and sets isLoading
appropriately so the UI can show the new state.
_onSearchPressed()
configures and initiates the search query. This should kick off when the Go button is pressed.
To accomplish that, go back to the render
method and replace the onPress
prop for the Go Button
as follows:
onPress={this._onSearchPressed}
Finally, add the following utility function just above the SearchPage
class declaration:
function urlForQueryAndPage(key, value, pageNumber) {
const data = {
country: 'uk',
pretty: '1',
encoding: 'json',
listing_type: 'buy',
action: 'search_listings',
page: pageNumber,
};
data[key] = value;
const querystring = Object.keys(data)
.map(key => key + '=' + encodeURIComponent(data[key]))
.join('&');
return 'https://api.nestoria.co.uk/api?' + querystring;
}
urlForQueryAndPage
doesn’t depend on SearchPage
, so it’s implemented as a free function rather than a method. It first creates the query string based on the parameters in data
. Then it transforms the data into name=value
pairs separated by ampersands. Finally, it calls the Nestoria API to return the property listings.
Save your changes, head back to the simulator and press Go. You’ll see the activity indicator spin:
Your Xcode console should show something like this:
2017-11-26 23:19:21.950 [info][tid:com.facebook.react.JavaScript] https://api.nestoria.co.uk/api?country=uk&pretty=1&encoding=json&listing_type=buy&action=search_listings&page=1&place_name=london
Copy and paste that URL into your browser to see the result. You’ll see a massive JSON object. Don’t worry — you don’t need to understand that! You’ll add code to parse that now.
Performing an API Request
Still within SearchPage.js, update the initial state in the class constructor to add a message
variable to the end of the list:
message: '',
Within render
, add the following to the bottom of your UI, right after the spinner:
<Text style={styles.description}>{this.state.message}</Text>
You’ll use this to display a range of messages to the user.
Add the following code to the end of _executeQuery
:
fetch(query)
.then(response => response.json())
.then(json => this._handleResponse(json.response))
.catch(error =>
this.setState({
isLoading: false,
message: 'Something bad happened ' + error
}));
This makes use of the fetch
function, which is part of the Fetch API. The asynchronous response is returned as a Promise. The success path calls _handleResponse
which you’ll define next, to parse the JSON response.
Add the following function to SearchPage
:
_handleResponse = (response) => {
this.setState({ isLoading: false , message: '' });
if (response.application_response_code.substr(0, 1) === '1') {
console.log('Properties found: ' + response.listings.length);
} else {
this.setState({ message: 'Location not recognized; please try again.'});
}
};
This clears isLoading
and logs the number of properties found if the query was successful.
Note: Nestoria has a number of non-1** response codes that are potentially useful. For example, 202 and 200 return a list of best-guess locations.
Note: Nestoria has a number of non-1** response codes that are potentially useful. For example, 202 and 200 return a list of best-guess locations.
Save your changes, head back to the simulator and press Go. You should see an Xcode console log message saying that 20 properties (the default result size) were found:
2017-11-26 23:22:18.478 [info][tid:com.facebook.react.JavaScript] Properties found: 20
Also note that when this message is logged, the spinner goes away.
It’s time to see what those 20 properties actually look like!
Displaying the Results
Create a new file SearchResults.js, and add the following:
'use strict';
import React, { Component } from 'react'
import {
StyleSheet,
Image,
View,
TouchableHighlight,
FlatList,
Text,
} from 'react-native';
This imports the relevant modules you’ll use.
Next, add the component:
export default class SearchResults extends Component<{}> {
_keyExtractor = (item, index) => index;
_renderItem = ({item}) => {
return (
<TouchableHighlight
underlayColor='#dddddd'>
<View>
<Text>{item.title}</Text>
</View>
</TouchableHighlight>
);
};
render() {
return (
<FlatList
data={this.props.listings}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
);
}
}
The above code makes use of a more specialized component — FlatList
— which displays rows of data within a scrolling container, similar to UITableView
. Here’s a look at the FlatList
properties:
-
data
provides the data to display -
keyExtractor
provides a unique key that React uses for efficient list item management -
renderItem
specifies how the UI is rendered for each row
Save your new file.
Add the following to SearchPage.js just beneath the import
statements:
import SearchResults from './SearchResults';
This brings in the newly added SearchResults
class.
In _handleResponse
, replace the console.log
statement with the following:
this.props.navigator.push({
title: 'Results',
component: SearchResults,
passProps: {listings: response.listings}
});
This navigates to your newly added SearchResults
component and passes in the listings from the API request. Using the push
method ensures the search results are pushed onto the navigation stack, which means you’ll get a Back button to return to the root.
Save your changes, head back to the simulator and press Go. You’ll be greeted by a list of properties:
It’s great to see the property listings, but that list is a little drab. Time to liven things up a bit.
A Touch of Style
Add the following style definition at the end of SearchResults.js:
const styles = StyleSheet.create({
thumb: {
width: 80,
height: 80,
marginRight: 10
},
textContainer: {
flex: 1
},
separator: {
height: 1,
backgroundColor: '#dddddd'
},
price: {
fontSize: 25,
fontWeight: 'bold',
color: '#48BBEC'
},
title: {
fontSize: 20,
color: '#656565'
},
rowContainer: {
flexDirection: 'row',
padding: 10
},
});
This defines all the styles that you are going to use to render each row.
Add a new component representing a row by adding the following just under the import statements:
class ListItem extends React.PureComponent {
_onPress = () => {
this.props.onPressItem(this.props.index);
}
render() {
const item = this.props.item;
const price = item.price_formatted.split(' ')[0];
return (
<TouchableHighlight
onPress={this._onPress}
underlayColor='#dddddd'>
<View>
<View style={styles.rowContainer}>
<Image style={styles.thumb} source={{ uri: item.img_url }} />
<View style={styles.textContainer}>
<Text style={styles.price}>{price}</Text>
<Text style={styles.title}
numberOfLines={1}>{item.title}</Text>
</View>
</View>
<View style={styles.separator}/>
</View>
</TouchableHighlight>
);
}
}
This manipulates the returned price, which is in the format 300,000 GBP, to remove the GBP suffix. Then it renders the row UI using techniques that you are by now quite familiar with. Of note, an Image
is added to the row and is loaded from a returned URL (item.img_url
) which React Native decodes off the main thread.
React.PureComponent
. React re-renders a Component
if its props or state changes. React only re-renders a PureComponent
if a shallow compare of the state and props shows changes. Used under the right conditions, this can give your app a performance boost.Now replace _renderItem
with the following:
_renderItem = ({item, index}) => (
<ListItem
item={item}
index={index}
onPressItem={this._onPressItem}
/>
);
_onPressItem = (index) => {
console.log("Pressed row: "+index);
};
_onPressItem
is passed into ListItem
to handle a row selection. This design pattern is equivalent to a callback. In this callback, the index for the selected row is logged.
Save your work, head back to the simulator, press Go, and check out your results:
That looks a lot better — although it’s a wonder anyone can afford to live in London!
Tap the first row and check that the Xcode console reflects the selection:
2017-11-26 23:29:19.943 [info][tid:com.facebook.react.JavaScript] Pressed row: 0
Try tapping other listings or searching other locations in the UK.