How to add a Search bar in a FlatList in React Native apps

Published on Apr 16, 2020

11 min read

EXPO

cover_image

Originally published at Crowdbotics.com

There are few ways to create scrollable lists in React Native. Two of the common ways available in React Native core are ScrollView and FlatList components. Each has its strength and in this tutorial, let us dive deep to create a search bar with FlatList component.

The final result you are going to achieve at the end of this tutorial is shown below.

ss8

Table of contents

🔗
  • Getting started
  • What is FlatList?
  • Basic usage of a FlatList component
  • Fetching data from Remote API in a FlatList
  • Adding a custom Separator to FlatList component
  • Adding a Search bar
  • Run the app
  • Add clear button to input text field
  • Conclusion

Getting started

🔗

For the demo we are going to create in this tutorial, I am going to use Expo. You are free to choose and use anything between an Expo CLI or a react-native-cli.

To start, let us generate a React Native app using Expo CLI and then install the required dependency to have a charming UI for the app. Open up a terminal window and run the following commands in the order they are mentioned.

expo init searchbarFlatList
cd searchbarFlatList
yarn install @ui-kitten/components @eva-design/eva lodash.filter
expo install react-native-svg

Note: The dependency react-native-svg is required as a peer dependency for the UI kitten library.

UI Kitten is ready to use now. To check, everything has installed correctly, let us modify App.js file as the following snippet:

1import React from 'react';
2import { ApplicationProvider, Layout, Text } from '@ui-kitten/components';
3import { mapping, light as lightTheme } from '@eva-design/eva';
4
5const HomeScreen = () => (
6 <Layout style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
7 <Text category="h1">HOME</Text>
8 </Layout>
9);
10
11const App = () => (
12 <ApplicationProvider mapping={mapping} theme={lightTheme}>
13 <HomeScreen />
14 </ApplicationProvider>
15);
16
17export default App;

The ApplicationProvider accepts two props, mapping and theme.

To run this demo, open up the terminal window and execute the following command.

expo start

I am using an iOS simulator for the demo. Here is the output of the above code snippet.

ss1

What is FlatList?

🔗

The component FlatList is an efficient way to create scrolling data lists in a React Native app. It has a simple API to work with and is more efficient and preferment with a large amount of information to display in comparison to its alternate.

By default, you can just pass in an array of data and this component will do its work. You do not have to take care of formatting the data too often.

Basic usage of a FlatList component

🔗

There are three primary props that a FlatList component requires to display a list of data:

  • data: an array of data that is used to create a list. Generally, this array is built of multiple objects.
  • renderItem: is a function that takes an individual element from the data array and renders it on the UI.
  • keyExtractor: it tells the list of data to use the unique identifiers or id for an individual element.

To get understand this pragmatically, let us build a mock an array of data and using FlatList, let us display it on our demo app. To start, import the following statements in App.js file.

1import React from 'react';
2import { FlatList, View, Text } from 'react-native';

Then, create an array of mock data.

1const mockData = [
2 { id: '1', text: 'Expo 💙' },
3 { id: '2', text: 'is' },
4 { id: '3', text: 'Awesome!' }
5];

Now, modify the HomeScreen component with the following snippet:

1const HomeScreen = () => (
2 <View
3 style={{
4 flex: 1,
5 paddingHorizontal: 20,
6 paddingVertical: 20,
7 marginTop: 40
8 }}
9 >
10 <FlatList
11 data={mockData}
12 keyExtractor={item => item.id}
13 renderItem={({ item }) => (
14 <Text style={{ fontSize: 22 }}>
15 {item.id} - {item.text}
16 </Text>
17 )}
18 />
19 </View>
20);

If the Expo cli command to run the development server is still running, you are going to get the following result.

ss2

Fetching data from Remote API in a FlatList

🔗

You can even play around with it. Try to fetch data from a real-time remote API and display them in the list instead of mock data. For a start, you can use a public API URL such as Randomuser.me API. The result to obtain at the end of this section is displayed below.

ss3

Open, App.js file and a state object with some properties to keep track of data from the Random User API. Also, do not forget to modify the import statements.

1// modify the import statements as below
2import React from 'react';
3import {
4 FlatList,
5 View,
6 ActivityIndicator,
7 TouchableOpacity
8} from 'react-native';
9import { ApplicationProvider, Text, Avatar } from '@ui-kitten/components';
10import { mapping, light as lightTheme } from '@eva-design/eva';
11
12// add a state object to the HomeScreen component
13class HomeScreen extends React.Component {
14 state = {
15 loading: false,
16 data: [],
17 page: 1,
18 seed: 1,
19 error: null
20 };
21
22 // ... rest of the code
23}

With the HTTP request to the API URL, let us fetch the first 20 results for now. Create a handler method called makeRemoteRequest that uses JavaScript's fetch(url) where url is the API request. It will fetch the results in JSON format. In case of a successful response from the API, the loading indicator (which is going to add later) will be false.

Also, using the lifecycle method componentDidMount, you can render the list of random users at the initial render of the HomeScreen component.

1 componentDidMount() {
2 this.makeRemoteRequest()
3 }
4
5 makeRemoteRequest = () => {
6 const { page, seed } = this.state
7 const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`
8 this.setState({ loading: true })
9
10 fetch(url)
11 .then(res => res.json())
12 .then(res => {
13 this.setState({
14 data: page === 1 ? res.results : [...this.state.data, ...res.results],
15 error: res.error || null,
16 loading: false
17 })
18 })
19 .catch(error => {
20 this.setState({ error, loading: false })
21 })
22 }

Next, add a renderFooter handler method that is going to display a loading indicator based on the value from the state object. This indicator is shown when the list of data in still being fetched. When the value of this.state.loading is true, using the ActivityIndicator from react-native components, a loading indicator on the UI screen is shown.

1renderFooter = () => {
2 if (!this.state.loading) return null;
3
4 return (
5 <View
6 style={{
7 paddingVertical: 20,
8 borderTopWidth: 1,
9 borderColor: '#CED0CE'
10 }}
11 >
12 <ActivityIndicator animating size="large" />
13 </View>
14 );
15};

Here is the output you are going to get when the loading indicator is shown.

ss5

Adding a custom Separator to FlatList component

🔗

Previously, you learned about the three most important props in the FlatList component. It is so flexible that it comes with extra props to render different components to make UI as pleasing to the user. One such prop is called ItemSeparatorComponent. You can add your own styling with custom JSX.

To do so, add another handler method called renderSeparator. It consists of rendering a View with some styling.

1renderSeparator = () => {
2 return (
3 <View
4 style={{
5 height: 1,
6 width: '86%',
7 backgroundColor: '#CED0CE',
8 marginLeft: '5%'
9 }}
10 />
11 );
12};

This completes all of the handler method currently required. Now, let us replace the previous FlatList component in App.js file with the following snippet.

A list of user names is going to be rendered with an individual item as the user. When pressed it shows an alert message for now but in real-time app, it will go on to display the complete user profile or user's contact.

The individual items in the list are going to be separated by the renderSeparator method as well as each item is going to display a user image which is composed of Avatar component from react-native-ui-kitten. The data is coming from the state object.

1<FlatList
2 data={this.state.data}
3 renderItem={({ item }) => (
4 <TouchableOpacity onPress={() => alert('Item pressed!')}>
5 <View
6 style={{
7 flexDirection: 'row',
8 padding: 16,
9 alignItems: 'center'
10 }}
11 >
12 <Avatar
13 source={{ uri: item.picture.thumbnail }}
14 size="giant"
15 style={{ marginRight: 16 }}
16 />
17 <Text
18 category="s1"
19 style={{
20 color: '#000'
21 }}
22 >{`${item.name.first} ${item.name.last}`}</Text>
23 </View>
24 </TouchableOpacity>
25 )}
26 keyExtractor={item => item.email}
27 ItemSeparatorComponent={this.renderSeparator}
28 ListFooterComponent={this.renderFooter}
29/>

From the above snippet, you can also notice that the loading indicator handler method renderFooter() is also used as the value of a prop called ListFooterComponent.

You can also use this prop to render other information at the bottom of all the items in the list. One example is to fetch more items in the list and show the loading indicator when the request is made.

Here is the output so far.

ss4

Adding a Search bar

🔗

To create a search bar on top of the FlatList, you need a component that scrolls away when the list is scrolled. One possible solution is to create a custom Search bar component and render it as the value of ListHeaderComponent prop in a FlatList.

Open App.js file and add the following prop to the list.

1<FlatList
2 // rest of the props remain same
3 ListHeaderComponent={this.renderHeader}
4/>

The search bar component is going to be an input field that can take the user's name from the end-user. To build one, let us start by modifying the import statements as below.

1import filter from 'lodash.filter';
2import {
3 ApplicationProvider,
4 Text,
5 Avatar,
6 Input
7} from '@ui-kitten/components';

Next, modify the state object and the following variables to it. The query is going to hold the search term when the input is provided. The fullData is a temporary array that a handler method is going to filter the user's name on the basis of a query.

1state = {
2 // add the following
3 query: '',
4 fullData: []
5};

Since you are already storing the results fetched from the remote API, state variable data, let us do the same for fullData as well. Add the following inside the handler method makeRemoteRequest().

1makeRemoteRequest = () => {
2 const { page, seed } = this.state;
3 const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
4 this.setState({ loading: true });
5
6 fetch(url)
7 .then(res => res.json())
8 .then(res => {
9 this.setState({
10 data: page === 1 ? res.results : [...this.state.data, ...res.results],
11 error: res.error || null,
12 loading: false,
13
14 // ---- ADD THIS ----
15 fullData: res.results
16 });
17 })
18 .catch(error => {
19 this.setState({ error, loading: false });
20 });
21};

Next, add the handler method 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.

1handleSearch = text => {
2 const formattedQuery = text.toLowerCase();
3 const data = filter(this.state.fullData, user => {
4 return this.contains(user, formattedQuery);
5 });
6 this.setState({ data, query: text });
7};

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().

1contains = ({ name, email }, query) => {
2 const { first, last } = name;
3 if (first.includes(query) || last.includes(query) || email.includes(query)) {
4 return true;
5 }
6 return false;
7};

Lastly, add renderHeader to render the search bar on the UI.

1renderHeader = () => (
2 <View
3 style={{
4 backgroundColor: '#fff',
5 padding: 10,
6 alignItems: 'center',
7 justifyContent: 'center'
8 }}
9 >
10 <Input
11 autoCapitalize="none"
12 autoCorrect={false}
13 onChangeText={this.handleSearch}
14 status="info"
15 placeholder="Search"
16 style={{
17 borderRadius: 25,
18 borderColor: '#333',
19 backgroundColor: '#fff'
20 }}
21 textStyle={{ color: '#000' }}
22 />
23 </View>
24);

That's it to add a search bar to the FlatList component.

Run the app

🔗

To run the app, make sure the expo start command is running. Next, go to Expo client and you are going to be prompted by the following screen:

ss6

Next, try to add a user name from the list being rendered.

ss7

Add clear button to input text field

🔗

The last thing I want to emphasize is that using a custom UI component from a UI library such as UI Kitten, you can use general TextInputProps from React Native core as well. A few examples are props such as autoCapitalize, and autoCorrect.

Let us add another prop called clearButtonMode that allows the input field to have a clear button appear on the right side. Add the prop to the Input inside renderHeader().

1<Input
2 // rest of the props remain same
3 clearButtonMode="always"
4/>

Now go back to the Expo client and see it in action

ss8

Conclusion

🔗

This brings an end to this current tutorial. The screen implemented in this demo is from one of the templates from Crowdbotics' react-native collection.

We use UI Kitten for our latest template libraries. Find more about how to create custom screens like this from our open source project here.

You can also find the source code from this tutorial at this Github repo.


More Posts

Browse all posts

Aman Mittal author

I'm a software developer and a technical writer. On this blog, I write about my learnings in software development and technical writing.

Currently, working maintaining docs at 𝝠 Expo. Read more about me on the About page.


Copyright ©  2019-2024 Aman Mittal · All Rights Reserved.