Rob Gleeson
Rob Gleeson

Reputation: 325

Filter array based on array of filters in Javascript

I have an array of users and also an array of filters.

I need to filter users based on the array of filters but when I do it will filter on one array but not the others

My filters object looks like this:

filters: {
  levels: [],
  locations: ['Laois'],
  subjects: ['Art']
}

My users look like this:

const users = [
  {
    email: '[email protected]',
    levels: ['Ordinary'],
    location: 'Laois',
    subjects: ['Art', 'German']
  },
  {
    email: '[email protected]',
    levels: ['Higher', 'Ordinary'],
    location: 'Laois',
    subjects: ['English', 'German']
  }
]

Based on the filter above, once filtered the users should only be one because it contains 'Art' and 'Laois':

[{
    email: '[email protected]',
    levels: ['Ordinary'],
    location: 'Laois',
    subjects: ['Art', 'German']
  },
]

But I am still getting the two users:

EDIT My Code:

applyFilter = (visable) => {
    let { filters, users } = this.state;
    const { subjects, locations, levels } = filters;


    let filteredUsers = [];
    const filteredData = users.filter((user) =>
      user.subjects.some(subject => subjects.includes(subject)) ||
      locations.some(location => location === user.location) ||
      user.levels.some(level => levels.includes(level))
    );

    console.log(filteredData)


    if (!subjects.length && !locations.length && !levels.length) {
      filteredUsers = users;
    } else {
      filteredUsers = filteredData;
    }

    this.setState({
      filterModalVisible: visable,
      filteredResults: filteredUsers.length >= 0 ? filteredUsers : [],
    });
  }

Upvotes: 2

Views: 1861

Answers (5)

Nikhil Aggarwal
Nikhil Aggarwal

Reputation: 28445

You can have a generic solution for any filter key by using Array.filter, Array.some and Array.includes

  • Use filter to validate each entry against all the filter conditions
  • If an filter condition has an empty list, continue to check for next condition
  • For locations, as the filter key is different from the key in object, add a specific check for that where we check for location to be included in the locations array
  • For other fields, check for existence of atleast 1 entry in array that exists in filter value. If there exists a value, continue to check for next condition.
  • After condition check, confirm if the entry is valid, if not break the loop and return false

let filters = {levels: [],locations: ['Laois'],subjects: ['Art']};
let users = [{email: '[email protected]',levels: ['Ordinary'],location: 'Laois',subjects: ['Art', 'German']},{email: '[email protected]',levels: ['Higher', 'Ordinary'],location: 'Laois',subjects: ['English', 'German']}];

users = users.filter(v => {
  let response = true;
  for (let filter in filters) {
    if(!filters[filter].length) continue;
    if(filter === "locations") {
      response = response && filters.locations.includes(v.location)
    } else response = response && v[filter].some(f => filters[filter].includes(f));
    if(!response) break;
  }
  return response;
});
console.log(users);

Upvotes: 1

AvcS
AvcS

Reputation: 2323

  1. You need to use && instead of || if you want to match all conditions
  2. As per your requirements, you need all of them to match if filter array is empty, so you can just bypass the check for that case

applyFilter = (visable) => {
    let { filters, users } = this.state;
    const { subjects, locations, levels } = filters;

    const filteredResults = users.filter((user) =>
        (!subjects.length || user.subjects.some(subject => subjects.includes(subject))) &&
        (!locations.length || locations.some(location => location === user.location)) &&
        (!levels.length || user.levels.some(level => levels.includes(level)))
    );

    this.setState({
        filterModalVisible: visable,
        filteredResults
    });
}

Upvotes: 0

ellipsis
ellipsis

Reputation: 12152

Using filter inside filter

const filters = {
  levels: [],
  locations: ['Laois'],
  subjects: ['Art']
}

const users = [
  {
    email: '[email protected]',
    levels: ['Ordinary'],
    location: 'Laois',
    subjects: ['Art', 'German']
  },
  {
    email: '[email protected]',
    levels: ['Higher', 'Ordinary'],
    location: 'Laois',
    subjects: ['English', 'German']
  }
]
console.log(users.filter((e) => {
  var arr = e.subjects.filter((x) => filters.subjects.includes(x))
  if (arr.length > 0 && filters.locations.includes(e.location))
    return e
}))

Upvotes: 0

Mamun
Mamun

Reputation: 68933

You can try with filter()

const filters = {
  levels: [],
  locations: ['Laois'],
  subjects: ['Art']
}

const users = [
  {
    email: '[email protected]',
    levels: ['Ordinary'],
    location: 'Laois',
    subjects: ['Art', 'German']
  },
  {
    email: '[email protected]',
    levels: ['Higher', 'Ordinary'],
    location: 'Laois',
    subjects: ['English', 'German']
  }
]

var res = users.filter(info => info.location==filters.locations[0] && info.subjects.includes(filters.subjects[0]));

console.log(res);

Upvotes: 1

Nenad Vracar
Nenad Vracar

Reputation: 122027

You can do this using filter and every methods by checking if every element from the filters value exist in the user value with the same key.

const filters = {"levels":[],"locations":["Laois"],"subjects":["Art"]}
const users = [{"email":"[email protected]","levels":["Ordinary"],"locations":"Laois","subjects":["Art","German"]},{"email":"[email protected]","levels":["Higher","Ordinary"],"locations":"Laois","subjects":["English","German"]}]

const res = users.filter(user => {
  return Object.entries(filters)
    .every(([key, value]) => {
      if (Array.isArray(value)) {
        return value.every(filter => {
          return user[key] && user[key].includes(filter)
        })
      } else {
        return user[key] == value
      }
    })
})

console.log(res)

Upvotes: 1

Related Questions