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 :
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
- Create the input form (TextInput)
- Search Google places for the keyword
- Get the top 5 closest predictions
- Display the predictions in a list (FlatList).
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
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',
},
});
Our app should look like this now
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,
});
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);
});
};
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"
}
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.
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}
/>
);
}
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>
);
}
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>
}
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.
🎉 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:
- wrap the
TextInput
andFlatList
with a View and give that view a highzIndex
of say 10 so it shows in front of anything below it. - 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,
},
And that's it 🎉Our search perfectly works now
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',
},
});
Here's the link to the complete example repo
https://github.com/mosoakinyemi/autocomplete-example
Good luck!
Top comments (7)
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
Apologies for the late response @ismael .
I guess you might wanna check your proxy or internet settings.
Good luck!
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?
Hi , I am getting an erorr saying Each child in a list should have a unique "key" prop.
Can you help me out ?
Sure, feel free to try something like:
keyExtractor={(item,index) => item.id+index}
cc @kshitizvaya
Thank you very much Akinyemi, This has been so helpful