Add a Search Bar Using Hooks and FlatList in React Native
August 28th, 2020 | By Aman Mittal | 8 min read
In this tutorial, let us explore different props provided by FlatList to fetch data, display data, and add a search bar.
A frequent use case for displaying data when developing mobile apps with React Native is 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 strengths.
Prerequisites
To follow this tutorial, please ensure you are familiar with JavaScript/ES6 and meet the following requirements in your local development environment:
Node.js version >= 12.x.x installed
Have access to one package manager, such as npm or yarn
the 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 order 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, which can lead to degraded performance 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 the FlatList to use a unique identifier, or an id, 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 style 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, the 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 the 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 in 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 in 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, enter a search query is to get results based on that query.
Conclusion
The focus of this tutorial was to get you familiarized with the 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.
Don't forget to pay special attention if you're developing commercial React Native apps that contain sensitive logic.
You can protect your React Native apps against code theft, tampering, and reverse engineering.
Jscrambler
The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.
View All ArticlesMust read next
Implementing Infinite Scroll with React Query and FlatList in React Native
In this tutorial, let's learn how to implement an infinite scroll using the FlatList component in React Native.
January 20, 2022 | By Aman Mittal | 9 min read
Build a Chatbot with Dialogflow and React Native
Learn how to build your first chatbot with Dialogflow and React Native and improve the user experience in your next mobile app.
March 26, 2019 | By Aman Mittal | 9 min read