Add a Search Bar Using Hooks and FlatList in React Native

Aman Mittal - Sep 2 '20 - - Dev Community

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
Enter fullscreen mode Exit fullscreen mode

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.

Hooks and Flatlist in React Native

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' }
];
Enter fullscreen mode Exit fullscreen mode

Next, import the FlatList in the App.js file.

import { StyleSheet, Text, View, FlatList } from 'react-native';
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
  }
});
Enter fullscreen mode Exit fullscreen mode

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.

Hooks and Flatlist in React Native

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';
Enter fullscreen mode Exit fullscreen mode

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``;
Enter fullscreen mode Exit fullscreen mode

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);

  // ...
}
Enter fullscreen mode Exit fullscreen mode

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);
      });
  }, []);
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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>
    );
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
  }
});
Enter fullscreen mode Exit fullscreen mode

The following is the list of contacts displayed using a FlatList that we are going to get after this step.

Hooks and Flatlist in React Native

Here is the loading indicator when the data is being fetched.

Hooks and Flatlist in React Native

And below is the scenario when the app is unable to fetch the data.

Hooks and Flatlist in React Native

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';
Enter fullscreen mode Exit fullscreen mode

Add the prop to the FlatList as shown below:

<FlatList
  ListHeaderComponent={renderHeader}
  // ... rest of the props remain same
/>
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Here is the output in the simulator after this step.

Hooks and Flatlist in React Native

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([]);
Enter fullscreen mode Exit fullscreen mode

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);
    });
}, []);
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

Now, in the simulator, a search query to get results based on that query.

Hooks and Flatlist in React Native

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .