Search Filter with React Native on FlatList

Viewed 79325

I am trying to search through a flatlist based on a search bar text. The problem I am running into is that when the user mistypes...say they wanted to type "burger" but typed "burget" by mistake then it returns nothing as it should. When the user deletes the "t" then it should re-render the flatlist again with the last text matching the "burge" part.

note: using react-native-elements search bar which allows me to call the text with just e or event.

What I have so far in the Main.js file:

searchText = (e) => {
    let text = e.toLowerCase();
    let trucks = this.state.data;

    // search by food truck name
    let filteredName = trucks.filter((truck) => {
      return truck.name.toLowerCase().match(text); 
    });

    // if no match and text is empty
    if(!text || text === '') {
      console.log('change state');
        this.setState({
          data: initial
        });
      }
    // if no name matches to text output
    else if(!Array.isArray(filteredName) && !filteredName.length) {
      console.log("not name");
      this.setState({
        data: [],
      });
    }
    // if name matches then display
    else if(Array.isArray(filteredName)) {
      console.log('Name');
      this.setState({
        data: filteredName,
      });
    }
   };

<View style={styles.container}>
  <SearchBar
    round
    lightTheme
    containerStyle={styles.search}
    ref="search"
    textInputRef="searchText"
    onChangeText={this.searchText.bind(this)}
    placeholder='Search by Truck Name...'
   />
   <TruckList getTruck={(truck) => this.setTruck(truck)} truckScreen={this.truckScreen} data={this.state.data}/>
</View>

then the TruckList.JS:

export default class TruckList extends Component {
    // rendering truck screen
    renderTruckScreen = (item) => {
        this.props.truckScreen();
        this.props.getTruck(item);
    }

    render() {
        return(
            <List style={styles.list}>
                <FlatList
                    data={this.props.data}
                    renderItem={({ item }) => (
                        <ListItem
                            roundAvatar
                            avatar={{uri: item.pic1}}
                            avatarStyle={styles.avatar}
                            title={item.name}
                            titleStyle={styles.title}
                            subtitle={
                                <View style={styles.subtitleView}>
                                    <Text style={styles.subtitleFood}>{item.food}</Text>
                                    <View style={styles.subtitleInfo}>
                                        <Icon 
                                            name="favorite"
                                            size={20}
                                            color={"#f44336"}
                                            style={styles.subtitleFavorite}
                                        />
                                        <Text style={styles.subtitleFavoriteText}>{item.favorited} favorited</Text>
                                    </View>
                                </View>
                            }
                            onPress={() => this.renderTruckScreen(item)}
                        />
                    )}
                    keyExtractor={(item) => item.uid}
                    ListFooterComponent={this.footer}
                />
            </List>
        )
      }
    }

I have tried a few other ways to no avail. Also the only solutions I have seen working for React Native are with ListView which will be depreciated in time. So I am trying to do this with the new FlatList Component.

Thanks for your help!

11 Answers

For a useful in-memory search you should keep initial data seperately.

I have more simple solution for this.

This solution for in-memory search on FlatList's data and uses it String.prototype​.includes() method to search substring.

You can find full source code of this component in this gist; https://gist.github.com/metehansenol/46d065b132dd8916159910d5e9586058

My initial state;

this.state = {
  searchText: "",
  data: [],
  filteredData: []
};

My SearchBar component (it comes from react-native-elements package);

<SearchBar
  round={true}
  lightTheme={true}
  placeholder="Search..."
  autoCapitalize='none'
  autoCorrect={false}
  onChangeText={this.search}
  value={this.state.searchText}
/>

My search method;

search = (searchText) => {
  this.setState({searchText: searchText});

  let filteredData = this.state.data.filter(function (item) {
    return item.description.includes(searchText);
  });

  this.setState({filteredData: filteredData});
};

And last my FlatList's DataSource expression;

<FlatList
  data={this.state.filteredData && this.state.filteredData.length > 0 ? this.state.filteredData : this.state.data}
  keyExtractor={(item) => `item-${item.id}`}
  renderItem={({item}) => <ListItem
    id={item.id}
    code={item.code}
    description={item.description}
  />}
/>

Happy coding...

Make Search Bar Filter for List View Data in React Native

For Real-Time Searching in List View using Search Bar Filter

  • We will load the list from the network call and then show it to the user.
  • The user can search the data by entering the text in TextInput.
  • After inserting the text SearchFilterFunction will be called We will compare the list data with the inserted data and will make a new Data source.
  • We will update the data source attached to the ListView.
  • It will re-render the list and the user will be able to see the filtered data.

//This is an example code to Add Search Bar Filter on Listview//
import React, { Component } from 'react';
//import react in our code.
 
import {
  Text,
  StyleSheet,
  View,
  FlatList,
  TextInput,
  ActivityIndicator,
  Alert,
} from 'react-native';
//import all the components we are going to use.
 
export default class App extends Component {
  constructor(props) {
    super(props);
    //setting default state
    this.state = { isLoading: true, text: '' };
    this.arrayholder = [];
  }
 
  componentDidMount() {
    return fetch('https://jsonplaceholder.typicode.com/posts')
      .then(response => response.json())
      .then(responseJson => {
        this.setState(
          {
            isLoading: false,
            dataSource: responseJson
          },
          function() {
            this.arrayholder = responseJson;
          }
        );
      })
      .catch(error => {
        console.error(error);
      });
  }
  SearchFilterFunction(text) {
    //passing the inserted text in textinput
    const newData = this.arrayholder.filter(function(item) {
      //applying filter for the inserted text in search bar
      const itemData = item.title ? item.title.toUpperCase() : ''.toUpperCase();
      const textData = text.toUpperCase();
      return itemData.indexOf(textData) > -1;
    });
    this.setState({
      //setting the filtered newData on datasource
      //After setting the data it will automatically re-render the view
      dataSource: newData,
      text: text,
    });
  }
  ListViewItemSeparator = () => {
    //Item sparator view
    return (
      <View
        style={{
          height: 0.3,
          width: '90%',
          backgroundColor: '#080808',
        }}
      />
    );
  };
  render() {
    if (this.state.isLoading) {
      //Loading View while data is loading
      return (
        <View style={{ flex: 1, paddingTop: 20 }}>
          <ActivityIndicator />
        </View>
      );
    }
    return (
      //ListView to show with textinput used as search bar
      <View style={styles.viewStyle}>
        <TextInput
          style={styles.textInputStyle}
          onChangeText={text => this.SearchFilterFunction(text)}
          value={this.state.text}
          underlineColorAndroid="transparent"
          placeholder="Search Here"
        />
        <FlatList
          data={this.state.dataSource}
          ItemSeparatorComponent={this.ListViewItemSeparator}
          renderItem={({ item }) => (
            <Text style={styles.textStyle}>{item.title}</Text>
          )}
          enableEmptySections={true}
          style={{ marginTop: 10 }}
          keyExtractor={(item, index) => index.toString()}
        />
      </View>
    );
  }
}
const styles = StyleSheet.create({
  viewStyle: {
    justifyContent: 'center',
    flex: 1,
    marginTop: 40,
    padding: 16,
  },
  textStyle: {
    padding: 10,
  },
  textInputStyle: {
    height: 40,
    borderWidth: 1,
    paddingLeft: 10,
    borderColor: '#009688',
    backgroundColor: '#FFFFFF',
  },
});

Click Hear for more idea

Here is my solution:

You need to have a backup of your data

this.state = {
    data: [],
    backup: []
}

on search method

search = txt => {
    let text = txt.toLowerCase()
    let tracks = this.state.backup
    let filterTracks = tracks.filter(item => {
    if(item.name.toLowerCase().match(text)) {
      return item
    }
  })
  this.setState({ data: filterTracks })
}

Explanation: when calling setState on your data it will changed to current state and cannot be changed again.

So backup data will handle to filter your data.

ref - https://medium.freecodecamp.org/how-to-build-a-react-native-flatlist-with-realtime-searching-ability-81ad100f6699

constructor(props) {
super(props);
this.state = {
  data: [],
  value: ""
};

this.arrayholder = [];
}

Next fetching data :-

_fetchdata = async () => {
const response = await fetch("https://randomuser.me/api?results=10");
const json = await response.json();
this.setState({ data: json.results });

this.arrayholder = json.results;
};

Next define searchFilterFunction :-

searchFilterFunction = text => {
this.setState({
  value: text
});


const newData = this.arrayholder.filter(item => {
  const itemData = item.email.toLowerCase();

  const textData = text.toLowerCase();

  return itemData.indexOf(textData) > -1;
});

this.setState({ data: newData });
};

rendering searchView:-

    <TextInput
      style={{ height: 40, borderColor: "gray", borderWidth: 1 }}
      onChangeText={text => this.searchFilterFunction(text)}
    />

Don't forget to import TextInput from "react-native";

You can Search your data by following these steps :

<TextInput onChangeText={(text) => searchData(text)} value={input} />
***Please Note *searchData is my function whom I passing a text prop*** 
const searchData = (text) => {
    const newData = restaurantsData.filter((item) => {
        return item.title.search(text) > -1;
    });
    setRestaurantsData(newData);
    setInput(text);
};

Note RestaurantsData is my data array

FYI : data is the subtext to be searched, this is a basic search implemented as the data to be searched is looked into every list item of an array which is a copy of the actual array/array of objects and finally its state is set whether match found or not between 0 to (actualArray.length-1) and the temporary arrayData is rendered if there is at least one match else actualArray is rendered

implementSearch(data) {
    temp = [];
    var count = 0;
    var searchData = data.toUpperCase();
    var arr = this.state.personDetail;
    for (var i = 0; i < arr.length; i++) {
      var actualData = arr[i].name.toUpperCase();
      if (actualData.includes(searchData)) {
        temp.push(arr[i]);
        count++;
      }
    }
    this.setState({
      tempArray: temp,
      matches: count,
      searchValue: data
    });
  }

Hope this helps

My search method; from @metehan-senol

search = (searchText) => {
 this.setState({searchText: searchText});

 let filteredData = this.state.data.filter(function (item) {
   return item.description.includes(searchText);
 });

 this.setState({filteredData: filteredData});
};

the search method of could be simplify and Eslint proof like so

search = (searchText) => {
  const searched = searchText.toLowerCase();
  this.setState(prevState => ({
    searchText: searched,
    filteredData: prevState.data.filter(item =>
      item.description.toLowerCase().includes(searched)
    ),
  }));
}; 

Do filter by applying

let filterData= data.filter((item) => {
  return item.name.toLowerCase().match(text)
})
if (!text || text === '') {
  this.setState({
    datasource: initial
  })
} else if (!Array.isArray(filterData) && !filterData.length) {
  // set no data flag to true so as to render flatlist conditionally
  this.setState({
    noData: true
  })
} else if (Array.isArray(filterData)) {
  this.setState({
    noData: false,`enter code here`
    dataSource: filterData
  })`enter code here`
}

This is not the best solution in terms of performance, but if you do not have a large amount of data, then feel free to use this function:

  searchFilter () {
    return this.props.data.filter((item) => {
       const regex = new RegExp(this.state.searchInput, "gi")
       return item.label.match(regex);
    })
  }

And then in your FlatList component:

  <FlatList
    data={this.searchFilter()}
    renderItem={this.renderItem}
    keyExtractor={(item) => item.value}
  />
Related