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.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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:

reactparse-data1

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:

parse_add_place_name

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.

reactparse-bayarea

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:

reactparse-blank

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:

  1. Here you turn off isLoading to clear out the loading indicator.
  2. Here you check this.queryErrors, which is another method that ParseReact.Mixin exposes. The method returns a non-null object if there are errors; you’ve updated the message to reflect this.
  3. Here you check if there are no results returned; if so, you set the appropriate message.
  4. 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:

reactparse-narnia

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.

Christine Abernathy

Contributors

Christine Abernathy

Author

Over 300 content creators. Join our team.