Integrating Parse and React Native for iOS
Learn how to combine the power of a Parse backend and the flexibility of a React Native frontend in your iOS apps! 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
Integrating Parse and React Native for iOS
30 mins
- Getting Started
- The Parse+React Structure
- Modeling Your Property Data
- Creating Your Parse App
- Defining your Schema
- Adding Some Sample Data
- Swapping in Parse Calls
- Modifying the Query Logic
- Modifying the UI
- Handling the Results
- Adding Search Functionality
- Adding Location Queries
- Adding More Test Data
- Where to Go From Here?
Modifying the UI
Open PropertyView.js and remove the following line in render
:
var price = property.price_formatted.split(' ')[0];
You no longer have to worry about reformatting price data, since now you have control over the input data.
Modify the related display code, so that instead of accessing the price
variable you just deleted, it uses the price
property:
<View style={styles.heading}>
<Text style={styles.price}>${property.price}</Text>
Also notice that you’re now in US territory, so you’ve deftly changed the currency symbol from pounds to dollars. :]
Next, modify the code to access the image’s URI
as follows:
<Image style={styles.image}
source={{uri: property.img_url.url()}} />
You add the call to url()
to access the actual image data in Parse. Otherwise, you’d only get the string representation of the URL.
Open SearchResults.js and make a similar change in renderRow
by deleting this line:
var price = rowData.price_formatted.split(' ')[0];
You’ll have to modify the related display code like you did before. Since it’s now showing a dollar value, not a pound value, modify the code as follows:
<View style={styles.textContainer}>
<Text style={styles.price}>${rowData.price}</Text>
Next, update the image access as shown below:
<Image style={styles.thumb}
source={{ uri: rowData.img_url.url() }} />
Still in SearchResults.js, change rowPressed
to check a different property for changes:
rowPressed: function(propertyGuid) {
var property = this.props.listings
.filter(prop => prop.id === propertyGuid)[0];
Parse+React identifies unique rows through an id
property; therefore you’re using this property instead of the guid
.
Similarly, change the implementation of getInitialState
to the following:
getInitialState: function() {
var dataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1.id !== r2.id
});
return {
dataSource: dataSource.cloneWithRows(this.props.listings)
};
},
Finally, modify renderRow
to use id
:
<TouchableHighlight onPress={() => this.rowPressed(rowData.id)}
underlayColor='#dddddd'>
These changes will use id
instead of guid
to match up the data records properly.
You’re not quite ready to test your UI code changes. You’ll first need to modify the data fetching logic to transition to your new UI.
Handling the Results
Open SearchPage.js to properly handle your query results. You’ll be camped in this file for the rest of the tutorial, so get comfortable! :]
Earlier on in this tutorial, your data fetch simply logged the results. Remove the debug statement in render
as it’s no longer needed:
console.log(this.data.listings);
To properly handle the results, you’ll reset the loading flag in the SearchPage
component and navigate to the SearchResults
component with the listing data. Keep in mind that ParseReact.Mixin
forces a re-rendering of your component whenever the results return.
How can you detect that a fetched result triggered the rendering? Furthermore, where should you check this and trigger the navigation?
ParseReact.Mixin
exposes pendingQueries
, which returns an array with the names of the in-progress queries. During the search, you can check for a zero length array to indicate the results have returned and hook your completion check in componentDidUpdate
that triggers post-render.
Add the following method just above render
:
componentDidUpdate: function(prevProps, prevState) {
if (prevState.isLoading && (this.pendingQueries().length == 0)) {
this.setState({ isLoading: false });
this.props.navigator.push({
title: 'Results',
component: SearchResults,
passProps: { listings: this.data.listings }
});
}
},
This code first checks isLoading
and if true
, checks that the query results are in. If these conditions are met, you reset isLoading
and push SearchResults
with this.data.listings
passed to it.
It’s generally frowned upon to change state in componentDidUpdate
, since this forces another render call. The reason you can get away with this here is that the first forced render call doesn’t actually change the underlying view.
Keep in mind that React makes use of a virtual DOM and only updates the view if the render call changes any part of that view. The second render call triggered by setting isLoading
does update the view. That means you only get a single view change when results come in.
Press Cmd+R in the simulator, then tap Go and view your one lonely, yet very satisfying, result:
It’s not much fun returning every listing regardless of the search query. It’s time to fix this!
Adding Search Functionality
You may have noticed that your current data schema doesn’t support a search flow since there’s no way to filter on a place name.
There are many ways to set up a sophisticated search, but for the purposes of this tutorial you’re going to keep it simple: you’ll set up a new column that will contain an array of search query terms. If a text search matches one of the terms, you’ll return that row.
Go to your Data Browser and add a column named place_name
of type Array
, like so:
Click inside the place_name
field of the existing row and add the following data:
["campbell","south bay","bay area"]
Head back to your React Native code. Still in SearchPage.js, modify getInitialState
to add a new state variable for the query sent to Parse and also modify the default search string displayed:
getInitialState: function() {
return {
searchString: 'Bay Area',
isLoading: false,
message: '',
queryName: null,
};
},
Next, you’ll need to modify observe
to check for the existence of a place name query.
Add the following filter to your Parse.Query
to look for the place name:
observe: function(props, state) {
var listingQuery = (new Parse.Query('Listing')).ascending('price');
if (state.queryName) {
listingQuery.equalTo('place_name', state.queryName.toLowerCase());
}
return state.isLoading ? { listings: listingQuery } : null;
},
The equalTo
filter looks through the values of an array type and returns objects where a match exists. The filter you’ve defined looks at the place_name
array and returns Listing
objects where the queryName
value is contained in the array.
Now, modify _executeQuery
to take in a query argument and set the queryName
state variable:
_executeQuery: function(nameSearchQuery) {
this.setState({
isLoading: true,
message: '',
queryName: nameSearchQuery,
});
},
Then, modify onSearchPressed
to pass the search string from the text input:
onSearchPressed: function() {
this._executeQuery(this.state.searchString);
},
Finally, modify onLocationPressed
to pass in null
to _executeQuery
:
onLocationPressed: function() {
navigator.geolocation.getCurrentPosition(
location => {
this._executeQuery(null);
},
error => {
this.setState({
message: 'There was a problem with obtaining your locaton: ' + error
});
}
);
},
You do this as you don’t want to execute a place name search when a location query triggers.
In your simulator, press Cmd+R; your application should refresh and you should see the new default search string.
Tap Go and verify that you get the same results as before.
Now go back to the home page of your app, enter neverland in the search box and tap Go:
Uh-oh. Your app pushed the new view with an empty result set. This would be a great time to add some error handling! :]
Update componentDidUpdate to the following implementation:
componentDidUpdate: function(prevProps, prevState) {
if (prevState.isLoading && (this.pendingQueries().length == 0)) {
// 1
this.setState({ isLoading: false });
// 2
if (this.queryErrors() !== null) {
this.setState({ message: 'There was a problem fetching the results' });
} else
// 3
if (this.data.listings.length == 0) {
this.setState({ message: 'No search results found' });
} else {
// 4
this.setState({ message: '' });
this.props.navigator.push({
title: 'Results',
component: SearchResults,
passProps: {listings: this.data.listings}
});
}
}
},
Taking the code step-by-step you’ll see the following:
- Here you turn off
isLoading
to clear out the loading indicator. - Here you check
this.queryErrors
, which is another method thatParseReact.Mixin
exposes. The method returns a non-null object if there are errors; you’ve updated the message to reflect this. - Here you check if there are no results returned; if so, you set the appropriate message.
- If there are no errors and there is data, push the results component.
Press Cmd+R and test the empty results case once again. You should now see the relevant message without the empty results component pushed:
Feel free to add more rows to your Listing
class in the Parse Data Browser to test additional search queries; you can make use of the sample photos available in the Media folder you downloaded earlier.