Richard Desouza
Richard Desouza

Reputation: 259

How to filter out a state array values based on elements located in another array in state?

I have state data that looks like this:

 state = {
      search: “”
      tagValue: “”
      data: [
           {  
              id: 1,
              fName: "Henry",
              lName: "Jones"
              email: “[email protected]”,
              gpa: “3.6”
              tag: [
                   "hello", "bye"
              ] 
           },
           {  
              fName: "Jeffrey",
              lName: "Johnston”,
              email: “[email protected]”,
              gpa: “2.6”,
              tag: [
                   "hello", "bye"
              ] 
           },
           {  
              fName: "Henry",
              lName: "Jones"
              gpa: “1.9”
              tag: [
              ] 
           }
      ]
  }

I've figured out how to filter the data array based on user input by doing:

 let studentListFilter = this.state.data.filter((student) => {
       return (
         student.firstName
           .toLowerCase()
           .indexOf(this.state.search.toLowerCase()) !== -1 ||
         student.lastName
           .toLowerCase()
           .indexOf(this.state.search.toLowerCase()) !== -1
 }

and then returning studentListFilter in my render method. search is a state value updated based on user input. But I'm having trouble figuring out how to compare values located within an array, specifically tag in this case, to user input.

I've tried doing this to no avail:

let studentListFilter = this.state.data.filter((student) => {
      return (
        student.fName
          .toLowerCase()
          .indexOf(this.state.search.toLowerCase()) !== -1 ||
        student.lName
          .toLowerCase()
          .indexOf(this.state.search.toLowerCase()) !== -1 ||
        student.tag.map(
          (individualTag) =>
            individualTag
              .toLowerCase()
              .indexOf(this.state.tagValue.toLowerCase()) !== -1
        )
      );
    });

Any help would be great

Upvotes: 1

Views: 81

Answers (2)

Drew Reese
Drew Reese

Reputation: 202864

One of the issues is that when checking if any of the string values includes the filtering input is that all strings include the empty string.

console.log("test".indexOf('') !== -1);
console.log("test".includes(''));

So the problem arises when you have two filtering inputs and assume when they apply to the data. Because the above then one of your conditions is always true if one of the inputs is empty and thus the data is never filtered.

  1. Need to change assumption about empty filter strings, if they are empty then no filtering should occur, so only apply searches if filters strings are truthy, ie. non-empty strings.
  2. First conditional test if both filters active, to break ties
  3. Second test each condition individually
  4. Return true to not filter data at all

Filter logic:

this.state.data
  .filter(student => {
    if (this.state.search && this.state.tagValue) {
      return (
        (student.fName.toLowerCase().includes(this.state.search) ||
          student.lName.toLowerCase().includes(this.state.search)) &&
        student.tag.some(tag => tag.toLowerCase().includes(this.state.tagValue))
      );
    }
    if (this.state.search) {
      return (
        student.fName.toLowerCase().includes(this.state.search) ||
        student.lName.toLowerCase().includes(this.state.search)
      );
    }
    if (this.state.tagValue) {
      return student.tag.some(tag =>
        tag.toLowerCase().includes(this.state.tagValue)
      );
    }
    return true;
  })

As can be seen there is a lot of repetition, a bit more DRY approach:

{this.state.data
  .filter(({ fName, lName, tag }) => {
    const includesName = [fName.toLowerCase(), lName.toLowerCase()].some(
      name => name.includes(this.state.search)
    );
    const includesTag = tag.some(tag =>
      tag.toLowerCase().includes(this.state.tagValue)
    );

    if (search && tagValue) {
      return includesName && includesTag;
    }
    if (search) {
      return includesName;
    }
    if (tagValue) {
      return includesTag;
    }
    return true;
  })

Edit multi-property filter demo

Upvotes: 1

Prince Hernandez
Prince Hernandez

Reputation: 3731

basically you can use some for your use case:

something like this:

    let studentListFilter = this.state.data.filter((student) => {
      const matchFirstName = student.fName
        .toLowerCase()
        .indexOf(this.state.search.toLowerCase()) !== -1;

      const matchLastName = student.lName
        .toLowerCase()
        .indexOf(this.state.search.toLowerCase()) !== -1

      // will return true if one of your tags match the search
      const someFnCB = (individualTag) => {
 return individualTag.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1 
};


      const matchTag = student.tag.some(someFnCB)


      return matchFirstName || matchLastName || matchTag;
    });

Upvotes: 0

Related Questions