DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Akinyemi Mosolasi
Akinyemi Mosolasi

Posted on • Updated on

How to Create a Google AutoComplete form in React Native

Hi there, custom location search bars are usually part of app designs and if you want to build a custom location search bar in your app, this article is for you🌺.

There's an already existing, amazing package for google-autocomplete in react native, but if you want to build a more custom interface, follow this guide and see how you can do it.

Here's what we'll create at the end of this article :
completed project

I'm happy to let you know that Google already has an API for getting autocomplete data (predictions) for:

  • Addresses
  • Places
  • Zip Codes

According to Google:

The Place Autocomplete service is a web service that returns place predictions in response to an HTTP request. The service can be used to provide autocomplete functionality for text-based geographic searches, by returning places such as businesses, addresses and points of interest as a user types.

The Place Autocomplete API is usually in the following form:

https://maps.googleapis.com/maps/api/place/autocomplete/json?key=API_KEY&input=SEARCH_KEYWORD.

You'll need an API key to use this API. You can go ahead and get one here . You can also go through the google place API documentation for more information.

So what we'll be doing can be simplified into 4 steps

  1. Create the input form (TextInput)
  2. Search Google places for the keyword
  3. Get the top 5 closest predictions
  4. Display the predictions in a list (FlatList).

google autocomplete in react native

Are you ready?πŸ˜„

Create a new React Native projectπŸ”₯

react-native init autocompleteExample

Navigate into the project and it running with

cd autocompleteExample

react-native run-ios 
        or  
react-native run-android
Enter fullscreen mode Exit fullscreen mode

Step 1: Create the Input form

    import React, {Component} from 'react';
    import {View,TextInput,StyleSheet,SafeAreaView} from 'react-native';

    export default class App extends Component {
      render() {
        return (
          <SafeAreaView style={styles.container}>
            <TextInput
              placeholder="Search for an address"
              placeholderTextColor="#000"
              style={styles.searchBox}
              onChangeText={(text) => this.searchLocation(text)}
              value={this.state.searchKeyword}
            />
          </SafeAreaView>
        );
      }
    }
    const styles = StyleSheet.create({
      searchBox: {
        width: 340,
        height: 50,
        fontSize: 18,
        borderRadius: 8,
        borderColor: '#aaa',
        color: '#000',
        backgroundColor: '#fff',
        borderWidth: 1.5,
        paddingLeft: 15,
      },
      container: {
        flex: 1,
        backgroundColor: 'lightblue',
        alignItems: 'center',
      },
    });
Enter fullscreen mode Exit fullscreen mode

Our app should look like this now
text input added

Step 2: Search Google places for the keyword

Now let's write the code to search for predictions every time the user types in something.

We'll connect the search function to the onChangeText property of the TextInput, so it runs the searchLocation function anytime the user types in something.

In this function, we'll use Google's place autocomplete API to search any keyword the user types in and get an array of predictions.

If the request is successful, we run the function:

 this.setState({
   searchResults: response.data.predictions,
   isShowingResults: true,
 });
Enter fullscreen mode Exit fullscreen mode

This function passes the data we've received to this.state.searchResults and it also sets the boolean this.state.isShowingResults to true so that the FlatList (which we would be creating very soon) can be visible since we now have data.

  searchLocation = async (text) => {
    this.setState({searchKeyword: text});
    axios
      .request({
        method: 'post',
        url: `https://maps.googleapis.com/maps/api/place/autocomplete/json?key=${API_KEY}&input=${this.state.searchKeyword}`,
      })
      .then((response) => {
        console.log(response.data);
        this.setState({
          searchResults: response.data.predictions,
          isShowingResults: true,
        });
      })
      .catch((e) => {
        console.log(e.response);
      });
  };

Enter fullscreen mode Exit fullscreen mode

Firing this API request returns a response that contains an array labelled predictions which contains the list of the top 5 predictions for the keyword searched in JSON (JavaScript Object Notation) format.

For example, if I type the word "Chi", I would get a response that looks something like this:

{
    "predictions": [
        {
            "description": "Chicago, IL, USA",
            "id": "53eeb015d61056c54245a909c79862532fc953ad",
            "place_id": "ChIJ7cv00DwsDogRAMDACa2m4K8",
        },
        {
            "description": "China",
            "id": "10bfe2265f06933baf8c2f1e35bf3bf132d74377",
            "place_id": "ChIJwULG5WSOUDERbzafNHyqHZU",
        },
        {
            "description": "Chihuahua, Mexico",
            "id": "26f4ee675aa61f33dd0ffc296b582baf2e08fa3e",
            "place_id": "ChIJM0BIXZ1E6oYRex3dBqen8bc",
        },
        {
            "description": "Chile",
            "id": "c33bb522167791f4bf271ef9151c1d13a1dd1092",
            "place_id": "ChIJL68lBEHFYpYRHbkCERPhBQU",
        }
    ],
    "status": "OK"
}
Enter fullscreen mode Exit fullscreen mode

noteπŸ€: I've omitted some part of the response object in this JSON sample to keep it short and simple, there's usually a lot more information in the response objects.

The JSON response mainly contains two root elements:

  • status : this contains the metadata of the request.
  • predictions: this contains an array of places, with information about the place. See Google's Place Autocomplete Results for detailed information about these results.

The Places API usually returns up to 5 results.

Of particular interest within the results are the place_id elements, which can be used to request more specific details about the place via a separate query.

We'll save the predictions array to state (or redux) this.state.searchResults, then display the list of predictions from there.

See where this is going? πŸ˜‰.

Step 5: Displaying the autocomplete data

So now we have gotten the autocomplete/predictions data in a structure similar to the JSON sample above, which we have stored in this.state.searchResults.

To display the autocomplete data, we'll use a FlatList that would be placed below the TextInput to display the predictions.

Google location autocomplete FlatList breakdown

Let's do that quickly:


{
  this.state.isShowingResults && (
      <FlatList
        data={this.state.searchResults}
        renderItem={({item, index}) => {
          return (
            <TouchableOpacity
              style={styles.resultItem}
              onPress={() => this.setState({searchKeyword: item.description})}>
              <Text>{item.description}</Text>
            </TouchableOpacity>
          );
        }}
        keyExtractor={(item) => item.id}
        style={styles.searchResultsContainer}  
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

In this part where we show the list, we'll utilize a concept known as Short-circuit evaluation in javascript.

{
    this.state.isShowingResults && (
    <View>
    //Our List
    </View>
    );
}
Enter fullscreen mode Exit fullscreen mode

In simple terms, it means the rest of the code (which displays the View that contains the list) will only be executed if the first argument (this.state.isShowingResults) is true.

For example, the Text "Hello there🐣" will only be rendered if the value of this.state.isShowingText is true

{
    this.state.isShowingText &&
    <Text> Hello there🐣 </Text> 
}
Enter fullscreen mode Exit fullscreen mode

Hope you're understanding😊, and if you're not, kindly leave a comment.

This short-circuiting is done to make sure we only show the list when we've got data from the autocomplete API request.

So our app should look like this now.

displacing a view

πŸŽ‰ Hooray, our autocomplete now works.

But we still have a little problem. What if there's some other component below the TextInput?

Notice how the FlatList displaces the pink dummy View below it?

To solve this, we do two things:

  1. wrap the TextInput and FlatList with a View and give that view a high zIndex of say 10 so it shows in front of anything below it.
  2. Make the FlatList absolute so it shows on top of any view or element below it.

Like this:

...
<View style={styles.autocompleteContainer}>
    <TextInput .../>
    <FlatList ... />
</View>

//...styles
autocompleteContainer: {
    zIndex: 10,
},
searchResultsContainer: {
    width: 340,
    height: 200,
    backgroundColor: '#fff',
    position: 'absolute',
    top: 50,
},
Enter fullscreen mode Exit fullscreen mode

And that's it πŸŽ‰Our search perfectly works now

completed project

Here's our complete code:

import React, {Component} from 'react';
import {
  View,
  Text,
  FlatList,
  TouchableOpacity,
  TextInput,
  StyleSheet,
  SafeAreaView,
} from 'react-native';
import axios from 'axios';

const API_KEY = 'Your-API-Key';
export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      searchKeyword: '',
      searchResults: [],
      isShowingResults: false,
    };
  }

  searchLocation = async (text) => {
    this.setState({searchKeyword: text});
    axios
      .request({
        method: 'post',
        url: `https://maps.googleapis.com/maps/api/place/autocomplete/json?key=${API_KEY}&input=${this.state.searchKeyword}`,
      })
      .then((response) => {
        console.log(response.data);
        this.setState({
          searchResults: response.data.predictions,
          isShowingResults: true,
        });
      })
      .catch((e) => {
        console.log(e.response);
      });
  };

  render() {
    return (
      <SafeAreaView style={styles.container}>
        <View style={styles.autocompleteContainer}>
          <TextInput
            placeholder="Search for an address"
            returnKeyType="search"
            style={styles.searchBox}
            placeholderTextColor="#000"
            onChangeText={(text) => this.searchLocation(text)}
            value={this.state.searchKeyword}
          />
          {this.state.isShowingResults && (
            <FlatList
              data={this.state.searchResults}
              renderItem={({item, index}) => {
                return (
                  <TouchableOpacity
                    style={styles.resultItem}
                    onPress={() =>
                      this.setState({
                        searchKeyword: item.description,
                        isShowingResults: false,
                      })
                    }>
                    <Text>{item.description}</Text>
                  </TouchableOpacity>
                );
              }}
              keyExtractor={(item) => item.id}
              style={styles.searchResultsContainer}
            />
          )}
        </View>
        <View style={styles.dummmy} />
      </SafeAreaView>
    );
  }
}

const styles = StyleSheet.create({
  autocompleteContainer: {
    zIndex: 1,
  },
  searchResultsContainer: {
    width: 340,
    height: 200,
    backgroundColor: '#fff',
    position: 'absolute',
    top: 50,
  },
  dummmy: {
    width: 600,
    height: 200,
    backgroundColor: 'hotpink',
    marginTop: 20,
  },
  resultItem: {
    width: '100%',
    justifyContent: 'center',
    height: 40,
    borderBottomColor: '#ccc',
    borderBottomWidth: 1,
    paddingLeft: 15,
  },
  searchBox: {
    width: 340,
    height: 50,
    fontSize: 18,
    borderRadius: 8,
    borderColor: '#aaa',
    color: '#000',
    backgroundColor: '#fff',
    borderWidth: 1.5,
    paddingLeft: 15,
  },
  container: {
    flex: 1,
    backgroundColor: 'lightblue',
    alignItems: 'center',
  },
});
Enter fullscreen mode Exit fullscreen mode

Here's the link to the complete example repo
https://github.com/mosoakinyemi/autocomplete-example

Good luck!

Top comments (7)

Collapse
 
ifmael profile image
Ismael Rodriguez

I am trying to make a request to the Google API directly as you did it, but I get a CORS error so I've added "Access-Control-Allow-Origin": "*" but my request still failing.
Googling I found stackoverflow.com/questions/443367...

does your code work? and thanks for sharing your knowledge

Collapse
 
mosoakinyemi profile image
Akinyemi Mosolasi Author • Edited on

Apologies for the late response @ismael .
I guess you might wanna check your proxy or internet settings.

Good luck!

Collapse
 
sudani profile image
Abbas Hamza

this is great tut , i followed it to the dot everything is ok apart i do not have result showing when i type , any idea where did i go wrong i have an API key billing is set everything look ok any suggestion please?

Collapse
 
kshitizvaya profile image
kshitiz vaya

Hi , I am getting an erorr saying Each child in a list should have a unique "key" prop.
Can you help me out ?

Collapse
 
mosoakinyemi profile image
Akinyemi Mosolasi Author

Sure, feel free to try something like:
keyExtractor={(item,index) => item.id+index}

Collapse
 
mosoakinyemi profile image
Akinyemi Mosolasi Author
Collapse
 
raj360 profile image
Raymond Kalumba Joseph

Thank you very much Akinyemi, This has been so helpful

🌚 Life is too short to browse without dark mode