1

I have some JSON (see end) that contains two values. I need the search to filter based on either value. I documented every part of the search/display of code

Here is my useState variables:

const [dataBackup, setdataBackup] = useState([]) const [dataSource, setdataSource] = useState([])

Here is my FlatList:

 <SectionList
      contentContainerStyle={{ paddingHorizontal: 10}}
      stickySectionHeadersEnabled={false}
      sections={dataSource}
      keyExtractor={(item, index) => {
        return  index.toString();
       }}
      renderSectionHeader={({ section }) => (
        <>
          <Text style={styles.sectionHeader}>{section.title}</Text>
          {section.data ? (
            <OptimizedFlatList
              horizontal
              data={section.data} 
              keyExtractor={(item, index) => {
                return  index.toString();
               }}
              renderItem={({ item }) => <ListItem item={item} navigation={navigation} />}
              showsHorizontalScrollIndicator={false}
            />
          ) : null}
        </>
      )}
      renderItem={({ item, section, navigation }) => {
        if (section.data) {
          return null;
        }
        return <ListItem item={item} navigation={navigation}/>;
      }}
    />

Here is the NOT WORKING filtering logic:

    filterList = (text) => {
  for (let i = 0; i < dataBackup.length; i++) {
    const t = dataBackup[i];
    newData = t["data"].filter((item) => {
      const itemData = item["name"].toLowerCase();
      const textData = text.toLowerCase();
      setdataSource(itemData.indexOf(textData) > -1)
    });

  }

}

Here is what the data looks like:

[{
    title: 'Leg',
    data:[
        {
      "bodyPart": "lower legs",
      "equipment": "assisted",
      "gifUrl": "http://d205bpvrqc9yn1.cloudfront.net/1708.gif",
      "id": "1708",
      "name": "assisted lying calves stretch",
      "target": "calves",
      "broad_target": "legs",
      "ppl": "legs"
    },
    {
      "bodyPart": "lower legs",
      "equipment": "smith machine",
      "gifUrl": "http://d205bpvrqc9yn1.cloudfront.net/1396.gif",
      "id": "1396",
      "name": "smith toe raise",
      "target": "calves",
      "broad_target": "legs",
      "ppl": "legs"
    }
  ]
},

{
    title: 'Back',
    data:[
        {
      "bodyPart": "Back",
      "equipment": "assisted",
      "gifUrl": "http://d205bpvrqc9yn1.cloudfront.net/1708.gif",
      "id": "1708",
      "name": "Back1",
      "target": "Back",
      "broad_target": "Back",
      "ppl": "Pull"
    },
    {
      "bodyPart": "Back",
      "equipment": "smith machine",
      "gifUrl": "http://d205bpvrqc9yn1.cloudfront.net/1396.gif",
      "id": "1396",
      "name": "Back2",
      "target": "Back",
      "broad_target": "Back",
      "ppl": "Pull"
    }
  ]
}]

I want it to filter by Title OR by name

2 Answers2

1

So i do not see a title field on your data object. But this is how you can do it for finding exact match. The implementation is for filtering on name and equipment. If you need a partial match you can do item.name.toLowerCase().split(/\s+|./).includes(query.toLowerCase())

const dataSample = [
    { "title": "red", 
      "data": [{
          "bodyPart": "back",
          "broad_target": "back",
          "equipment": "cable",
          "gifUrl": "http://d205bpvrqc9yn1.cloudfront.net/0007.gif",
          "id": 1,
          "name": "alternate item",
          "ppl": "pull",
          "target": "lats",
          "thumbnail": "https://images.unsplash.com/photo-1640334554717-a7a4ce5d0045?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=296&q=20"
        }
      ]
    },
    { "title": "blue", 
      "data": [{
          "bodyPart": "back",
          "broad_target": "back",
          "equipment": "cable",
          "gifUrl": "http://d205bpvrqc9yn1.cloudfront.net/0007.gif",
          "id": 1,
          "name": "alternate",
          "ppl": "pull",
          "target": "lats",
          "thumbnail": "https://images.unsplash.com/photo-1640334554717-a7a4ce5d0045?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=296&q=20"
        }
      ]
    }
]
    
const filterData = (data = [], query) =>
  data.filter(item => item.title.toLowerCase() === query.toLowerCase()
  ||
  item.data.find(element => element.name.toLowerCase() === query.toLowerCase())
  )
console.log(filterData(dataSample, 'alternate'))
console.log(filterData(dataSample, 'red'))

Try this out, updated as per your comment:

const filterData = (data = [], query) =>
data.filter(item => item.title.toLowerCase().includes(query.toLowerCase())
||
item.data.find(element => element.name.toLowerCase().includes(query.toLowerCase()))
)

<SearchBar 
    onChangeText={(text) =>
        text.length !== 0 ? 
        setdataSource(filterData(dataBackup, text)): 
        setdataSource(dataBackup)} 
    onClearPress={() => setdataSource(dataSource)} 
    />

When i gave it another thought, this is the perfect solution for your case covering all other scenarios.

useEffect(() => {
    console.log("search key", searchKey)
    if(searchKey.length != 0){
        setdataSource(filterData(dataBackup, searchKey))
    } else{
        setdataSource(dataBackup)
      } 
  }, [searchKey])

  const filterData = (data = [], query) => {
    
      const foundItems = searchForItem(data, query)
      const foundDataItems = searchForDataInItem(data, query)
      const uniqueItemTitles = [...new Set([...Object.keys(foundItems),...Object.keys(foundDataItems)])];
      return uniqueItemTitles.reduce((title, accumulator) => {
          const itemHadTitleMatchedToQuery = foundItems.find(element => element.title === title)
          const itemHadDataMatchedToQuery = foundDataItems.find(element => element.title === title)
          if(itemHadTitleMatchedToQuery) {
                accumulator.push({
                  ...itemHadTitleMatchedToQuery
                })
          } else {
                accumulator.push({
                  ...itemHadDataMatchedToQuery
                })
          }
          return accumulator;
      }, []);

  }

const searchForItem = (data = [], query) => {
    const result = data.filter(item => 
      item.title.toLowerCase().includes(query.toLowerCase())
      
  )

  return result
}

const searchForDataInItem = (data = [], query) => {
  return data.map(item => {
    return { ...item, data: item.data.filter(element => element.name.toLowerCase().includes(query.toLowerCase()))}
  }).filter(item => item.data.length != 0)
}
Dheeraj Sharma
  • 244
  • 1
  • 6
  • I updated my question with the correct JSON – branman3542 Mar 16 '22 at 09:34
  • Edited the solution. See if that helps! – Dheeraj Sharma Mar 16 '22 at 09:42
  • I believe the logic is working well, but given the flatlist and search, how do I incorporate the function? – branman3542 Mar 16 '22 at 09:53
  • I do not see your search component there, so i am not sure how you are triggering the search event, but every time user types something in text box and hits search, just call setDataSource(filterData(dataBackup, searchQuery)), and just copy that filterData function inside your component. It should work. – Dheeraj Sharma Mar 16 '22 at 09:58
  • i think its close, but doesn't work : ` { if (text.length !== 0){ setdataSource(filterData(dataBackup, text)) } else{ filterData(dataSource,"") } }} onClearPress={() => { filterData(dataSource,""); }} />` – branman3542 Mar 16 '22 at 10:22
  • Try this out, updated as per your comment: ``` const filterData = (data = [], query) => data.filter(item => item.title.toLowerCase().includes(query.toLowerCase()) || item.data.find(element => element.name.toLowerCase().includes(query.toLowerCase())) ) text.length !== 0 ? setdataSource(filterData(dataBackup, text)): setdataSource(dataBackup)} onClearPress={() => setdataSource(dataSource)} /> ``` – Dheeraj Sharma Mar 16 '22 at 11:03
  • This still doesn't work.. just to clarify, I should call dataSource with the FlatList, right? – branman3542 Mar 16 '22 at 16:27
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/242993/discussion-between-branman3542-and-dheeraj-sharma). – branman3542 Mar 16 '22 at 16:27
  • Hey! Also implement debounce for your search to give a smoother UX to your app. Read more about it here [react debounce on search inputs](https://stackoverflow.com/questions/23123138/perform-debounce-in-react-js). Also please mark the answer as accepted or closed if that resolves your problem – Dheeraj Sharma Mar 16 '22 at 18:30
  • hi @Dheeraj Sharma, there is an issue with your updated comment: `accumulator.push is not a function`. If I switch `(title, accumulator)` to `(accumulator, title)`, it says `cannot read item of undefined`. – branman3542 May 17 '22 at 07:36
  • Hey! @branman3542 you can’t switch positions of arguments on that callback function for reduce. accumulator.push exists because the initial object provided is a list there. – Dheeraj Sharma May 19 '22 at 18:01
  • Hi @Dheeraj Sharma, any idea why it says `accumulator.push is not a function`? – branman3542 May 19 '22 at 19:48
  • return uniqueItemTitles.reduce((title, accumulator) => { const itemHadTitleMatchedToQuery = foundItems.find(element => element.title === title) const itemHadDataMatchedToQuery = foundDataItems.find(element => element.title === title) if(itemHadTitleMatchedToQuery) { …. } return accumulator; }, []); <— are you passing this []? And can you make sure that’s a list and not any other object? Because if thats there then .push must be available. – Dheeraj Sharma May 20 '22 at 01:14
  • yes, the `[]` is there. – branman3542 May 21 '22 at 00:10
  • I think then there’s something wrong in the data itself that you’re trying to process. Can you share what exactly you have written that throws the error – Dheeraj Sharma May 21 '22 at 02:16
  • hi @Dheeraj Sharma, can I please get on a call with you for some more assistance? my LinkedIn is https://www.linkedin.com/in/brandon-jakobson/ if you want to message me to set it up! Thanks! – branman3542 Aug 01 '22 at 02:06
  • Hey! Sure. Let me know when you want to connect. – Dheeraj Sharma Aug 01 '22 at 16:22
0

After working with @Dheeraj Sharma, we came up with:

useEffect(() => {
    console.log("search key", searchKey)
    if(searchKey.length != 0){
        setdataSource(filterData(dataBackup, searchKey))
    } else{
        setdataSource(dataBackup)
      } 
  }, [searchKey])

  const filterData = (data = [], query) => {
    
      const foundItems = searchForItem(data, query)
      const foundData = searchForDataInItem(data, query)

      if(foundItems.length !== 0){
        return foundItems;
      }

      return foundData

  }

const searchForItem = (data = [], query) => {
    const result = data.filter(item => 
      item.title.toLowerCase().includes(query.toLowerCase())
      
  )

  return result
}

const searchForDataInItem = (data = [], query) => {
  return data.map(item => {
    return { ...item, data: item.data.filter(element => element.name.toLowerCase().includes(query.toLowerCase()))}
  }).filter(item => item.data.length != 0)
}