Add a Search Bar Using Hooks and FlatList in React Native
Aman Mittal
Posted on September 2, 2020
A common use case to display data when developing mobile apps with React Native is in the form of a list.
Two common ways exist in React Native to create lists: ScrollView
and FlatList
. Each of these components from the framework's API has its strength.
In this tutorial, let us explore different props provided by FlatList
to fetch data, display data, and add a search bar.
Prerequisites
To follow this tutorial, please make sure you are familiarized with JavaScript/ES6 and meet the following requirements on your local dev environment.
-
Node.js version >=
12.x.x
installed - Have access to one package manager such as npm or yarn
- expo-cli version installed or use npx
The example in the following tutorial is based on Expo SDK 38
.
Getting Started
To create a new Expo based project, open up a terminal window and run the following commands in the sequence they are described.
Make sure to install lodash.filter
after the project directory is generated. We are going to use the package to filter the data later when adding a search from the list functionality.
npx expo init [Project Name]
# choose a template when prompted
# this example is using the 'blank' template
# after the project directory has been generated
cd [Project Name]
# install dependency
yarn add lodash.filter
Once the new project is created and you have navigated inside it, run yarn start
. Whether you use a simulator or a real device, you are going to get the following result.
Using a FlatList Component
React Native's FlatList
is an efficient way to create scrolling lists that consist of a large amount of data without degrading the overall performance. It is optimized for large arrays of data because it renders only a set of items that are displayed on the screen. When scrolling through a list of data, the internal state is not preserved - as compared to ScrollView, which renders all the data immediately after mounting the component. This means that all the data in ScrollView is mounted on the device's memory and can lead to degraded performances when a large amount of data is being rendered.
Passing an array of data to the FlatList
is how you can display the list of data. Let's see how this works. For example, open App.js
and before the function component, add the following array of data.
const data = [
{ id: '1', title: 'First item' },
{ id: '2', title: 'Second item' },
{ id: '3', title: 'Third item' },
{ id: '4', title: 'Fourth item' }
];
Next, import the FlatList
in the App.js
file.
import { StyleSheet, Text, View, FlatList } from 'react-native';
The FlatList
is going to use three primary props that are required to display a list of data:
-
data
: an array of data that is used to create a list. The array consists of multiple objects as elements. -
keyExtractor
: tells theFlatList
to use a unique identifier or anid
for the individual elements of the array. -
renderItem
: a function that takes an individual element from the array of data and renders it on the UI.
Then, modify the App
component to return this list of data
.
export default function App() {
return (
<View style={styles.container}>
<Text style={styles.text}>Basic FlatList Example</Text>
<FlatList
data={data}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<View style={styles.listItem}>
<Text style={styles.listItemText}>{item.title}</Text>
</View>
)}
/>
</View>
);
}
Add the following styles
object.
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8f8f8',
alignItems: 'center'
},
text: {
fontSize: 20,
color: '#101010',
marginTop: 60,
fontWeight: '700'
},
listItem: {
marginTop: 10,
padding: 20,
alignItems: 'center',
backgroundColor: '#fff',
width: '100%'
},
listItemText: {
fontSize: 18
}
});
Now, go back to the simulator, and you are going to see that all objects inside the data
array are now displayed in the form of a list. Using FlatList
takes minimal effort to display organized data.
Fetching data from an API in FlatList
FlatList doesn't care about how the mobile app is fetching data. In the previous section, we did learn about how to mock an array of data and consume it as a list. In this section, let's fetch the data from a remote API resource and follow the same pattern (as in the previous section) to display the data.
Side Note: For a remote API resource, I am going to use the Random User Placeholder API.
Start by importing all the necessary components that we are going to use in this section. Update the following import statements as shown below:
import React, { useState, useEffect } from 'react';
import {
StyleSheet,
Text,
View,
FlatList,
ActivityIndicator,
Image
} from 'react-native';
Next, define the URL of the API endpoint to fetch the data from as a constant.
const API_ENDPOINT = `https://randomuser.me/api/?seed=1&page=1&results=20``;
The HTTP request to the API endpoint is going to fetch the first 20 results for now.
Define three state variables inside the App
component using the React Hook useState
. The isLoading
state variable is going to have a boolean value of false
by default. Its purpose is to display a loading indicator when the data is being fetched from the API endpoint.
The data
variable is going to have an empty array by default. Using this state variable, the FlatList
is populated to display a list of data.
The last state variable, error
, is going to have a default value of null
. It is only going to update when there is an error fetching the data.
export default function App() {
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState([]);
const [error, setError] = useState(null);
// ...
}
Next, using the React Hook useEffect
and the fetch
API from JavaScript, let's fetch the data from the API_ENDPOINT
. Add the following after you have defined the state variables inside the App
component.
The loading
variable is set to true
once the useEffect
instantiates. The boolean value of this variable is only set to false
either when the fetching of data is complete or when there is an error. The setData
below is updating the data
variable with an array of data.
export default function App() {
// state variables defined
useEffect(() => {
setIsLoading(true);
fetch(API_ENDPOINT)
.then(response => response.json())
.then(results => {
setData(results);
setIsLoading(false);
})
.catch(err => {
setIsLoading(false);
setError(err);
});
}, []);
// ...
}
Then add two if
conditions, each returning a JSX for two different scenarios. First, when the data is being fetched, a loading indicator is shown. Second, when there is an error, an error message is displayed.
export default function App() {
// state variables defined
// fetch data using useEffect
if (isLoading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#5500dc" />
</View>
);
}
if (error) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 18}}>
Error fetching data... Check your network connection!
</Text>
</View>
);
}
// ...
}
Then, update the FlatList
to display the user avatar and the name of the user fetched from the API endpoint.
export default function App() {
// ...
return (
<View style={styles.container}>
<Text style={styles.text}>Favorite Contacts</Text>
<FlatList
data={data}
keyExtractor={item => item.first}
renderItem={({ item }) => (
<View style={styles.listItem}>
<Image
source={{ uri: item.picture.thumbnail }}
style={styles.coverImage}
/>
<View style={styles.metaInfo}>
<Text style={styles.title}>{`${item.name.first} ${
item.name.last
}`}</Text>
</View>
</View>
)}
/>
</View>
);
}
Don't forget to update the styles
object as well.
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8f8f8',
alignItems: 'center'
},
text: {
fontSize: 20,
color: '#101010',
marginTop: 60,
fontWeight: '700'
},
listItem: {
marginTop: 10,
paddingVertical: 20,
paddingHorizontal: 20,
backgroundColor: '#fff',
flexDirection: 'row'
},
coverImage: {
width: 100,
height: 100,
borderRadius: 8
},
metaInfo: {
marginLeft: 10
},
title: {
fontSize: 18,
width: 200,
padding: 10
}
});
The following is the list of contacts displayed using a FlatList
that we are going to get after this step.
Here is the loading indicator when the data is being fetched.
And below is the scenario when the app is unable to fetch the data.
Add a search bar
In this section, let's create a search bar at the top of the current FlatList
. It provides a prop called ListHeaderComponent
to display a search bar.
Before we begin editing the App
component, let us add the necessary import statements required in this step. From react-native
, add the import for TextInput
. Also, import lodash.filter
.
import {
StyleSheet,
Text,
View,
FlatList,
ActivityIndicator,
Image,
TextInput
} from 'react-native';
import filter from 'lodash.filter';
Add the prop to the FlatList
as shown below:
<FlatList
ListHeaderComponent={renderHeader}
// ... rest of the props remain same
/>
Then define the renderHeader
function that is going to return the following JSX:
export default function App() {
//...
function renderHeader() {
return (
<View
style={{
backgroundColor: '#fff',
padding: 10,
marginVertical: 10,
borderRadius: 20
}}
>
<TextInput
autoCapitalize="none"
autoCorrect={false}
clearButtonMode="always"
value={query}
onChangeText={queryText => handleSearch(queryText)}
placeholder="Search"
style={{ backgroundColor: '#fff', paddingHorizontal: 20 }}
/>
</View>
);
}
// โฆ render JSX below
}
Here is the output in the simulator after this step.
Next, add two more state variables. First, query
is going to keep track of any input provided by the user to search through the list of data. It has a default value of empty string. Second, add another variable to hold the data from the API that is going to be used to filter the data.
const [query, setQuery] = useState('');
const [fullData, setFullData] = useState([]);
Update the side-effect useEffect
to populate the fullData
array.
useEffect(() => {
setIsLoading(true);
fetch(API_ENDPOINT)
.then(response => response.json())
.then(response => {
setData(response.results);
// ADD THIS
setFullData(response.results);
setIsLoading(false);
})
.catch(err => {
setIsLoading(false);
setError(err);
});
}, []);
Then, add a handler method called handleSearch
that is going to handle the search bar. By default, it is going to format the search term provided as a query to lowercase. The user's name is filtered from the state variable fullData
while the state variable data
stores the final results after the search to render the correct user.
The contains
handler method is going to look for the query. It accepts two parameters, the first and last name of the user and the formatted query to lowercase from handleSearch()
.
const handleSearch = text => {
const formattedQuery = text.toLowerCase();
const filteredData = filter(fullData, user => {
return contains(user, formattedQuery);
});
setData(filteredData);
setQuery(text);
};
const contains = ({ name, email }, query) => {
const { first, last } = name;
if (first.includes(query) || last.includes(query) || email.includes(query)) {
return true;
}
return false;
};
Now, in the simulator, a search query to get results based on that query.
Conclusion
The focus of this tutorial was to get you familiarized with different props that the FlatList
component provides.
Do note that it is recommended to use a powerful search provider such as Algolia when fetching data from an API endpoint for better results.
Finally, don't forget to pay special attention if you're developing commercial React Native apps that contain sensitive logic. You can protect them against code theft, tampering, and reverse engineering by following this guide.
Posted on September 2, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.