0

The code below works smoothly for the searches I made:

const selectedFilters = {
  color: ["Red", "Blue"],
  type: ["Shirt"],
  size: ["M"]
};

const items = [
  {
      name: "Item 1",
      filters: {
          color: ["Red", "Blue", "Black"],
          type: ["Shirt"],
          size: ["M"]
      }
  },
  {
      name: "Item 2",
      filters: {
          color: ["Red"],
          type: ["Pants"],
          size: ["M"]
      }
  }
];

const filterArr = Object.values(selectedFilters).flat();

const output = items.filter(({filters}) => {
  const objFilters = Object.values(filters).flat();
  return filterArr.every(val => objFilters.includes(val));
})

console.log(output);

On the other hand, when I search for 2 values I want it to work as "OR" not "AND"

e.g. when I search as

const selectedFilters = {
  color: ["Red"],
  type: ["Shirt", "Pants"],
  size: ["M"]
};

how can I make the engine to bring results containing shirts or pants, so both Item 1 and 2?

P.S. Currently an empty filter brings all the results, e.g.

const selectedFilters = {
      color: [],
      type: [],
      size: []
    };

which is something I want to keep as a feature.

2 Answers 2

1

One solution is to not flatten each filter and treat it independently with some

const selectedFilters = {
  color: ["Red"],
  type: ["Shirt", "Pants"],
  size: ["M"]
};

const items = [
  {
      name: "Item 1",
      filters: {
          color: ["Red", "Blue", "Black"],
          type: ["Shirt"],
          size: ["M"]
      }
  },
  {
      name: "Item 2",
      filters: {
          color: ["Red"],
          type: ["Pants"],
          size: ["M"]
      }
  }
];

const output = items.filter(({filters}) => {
  let isOK = true, k;
  for(k in selectedFilters){
    isOK = isOK && selectedFilters[k].some(val => filters[k].includes(val));
  }
  return isOK;
})

console.log(output);

EDIT Concerning you comment: I would personally prefer, for logical reasons, that when implementing filters, if one is present and empty it actually gives no result (there is no match after all). It could be useful later and more consistent than adding a special case for empty arrays that, if code is reused later and you forgot about this, can lead to unexpected behavior.

So my preferred solution would be to not add those filters beforehand, or give a falsey value (second one needs an extra check to prevent error when some is called):

not adding filters you want to skip:

const selectedFilters = {
  type: ["Shirt", "Pants"],
};

const items = [
  {
      name: "Item 1",
      filters: {
          color: ["Red", "Blue", "Black"],
          type: ["Shirt"],
          size: ["M"]
      }
  },
  {
      name: "Item 2",
      filters: {
          color: ["Red"],
          type: ["Pants"],
          size: ["M"]
      }
  }
];

const output = items.filter(({filters}) => {
  let isOK = true, k;
  for(k in selectedFilters){
    isOK = isOK && selectedFilters[k].some(val => filters[k].includes(val));
  }
  return isOK;
})

console.log(output);

allowing falsey values:

const selectedFilters = {
  color: false,
  type: ["Shirt", "Pants"],
  size: false
};

const items = [
  {
      name: "Item 1",
      filters: {
          color: ["Red", "Blue", "Black"],
          type: ["Shirt"],
          size: ["M"]
      }
  },
  {
      name: "Item 2",
      filters: {
          color: ["Red"],
          type: ["Pants"],
          size: ["M"]
      }
  }
];

const output = items.filter(({filters}) => {
  let isOK = true, k;
  for(k in selectedFilters){
    isOK = isOK && (!selectedFilters[k] || selectedFilters[k].some(val => filters[k].includes(val)));
  }
  return isOK;
})

console.log(output);

However if you really want the edge case for empty arrays, you can just test .length:

const selectedFilters = {
  color: [],
  type: ["Shirt", "Pants"],
  size: []
};

const items = [
  {
      name: "Item 1",
      filters: {
          color: ["Red", "Blue", "Black"],
          type: ["Shirt"],
          size: ["M"]
      }
  },
  {
      name: "Item 2",
      filters: {
          color: ["Red"],
          type: ["Pants"],
          size: ["M"]
      }
  }
];

const output = items.filter(({filters}) => {
  let isOK = true, k;
  for(k in selectedFilters){
    isOK = isOK && (!selectedFilters[k].length || selectedFilters[k].some(val => filters[k].includes(val)));
  }
  return isOK;
})

console.log(output);

Sign up to request clarification or add additional context in comments.

5 Comments

Great solution. But this modification removes one feature of the search: If user has not selected any color e.g. ``` const selectedFilters = { color: [], type: [], size: [] }; ``` was bringing all the results. How can I make that work again?
In your example, I want to search for pants and shirt, regardless their size and color feature, so it is: const selectedFilters = { color: [], type: ["Shirt", "Pants"], size: [] }; and the result comes empty. Just wanted to give an example @Kaddath
@mustafau. I have edited my answer, hope this is what you want
Great answer. Your support is the one needed for beginner developers like me, and also which this platform needs. Thank you so much! @Kaddath
I would kindly request your support about a detail. Some items in my items array does not have some features (e.g. "Item 2" has "color" and "type" but not "size") (My items have like 50+ features so..) If I filter any of these features which is not included in every array, it gives the error: Uncaught TypeError: Cannot read property 'includes' of undefined. How may I make the necessary change to make the filter ignore any parameter if it was not included in one of the arrays? @Kaddath P.S. I'm using your last version (example) which works for empty arrays with .length
1

Instead of flattening every filter array you can make a loop with Object.entries and then check if every product has an option in every selected filter.

const selectedFilters = {
  color: ["Red"],
  type: [],
  size: []
};

const items = [
  {
      name: "Item 1",
      filters: {
          color: ["Red", "Blue", "Black"],
          type: ["Shirt"],
          size: ["M"]
      }
  },
  {
      name: "Item 2",
      filters: {
          color: ["Orange"],
          type: ["Pants"],
          size: ["M"]
      }
  },
  {
      name: "Item 3",
      filters: {
          color: ["Red"],
          type: ["Pants"],
          size: ["M"]
      }
  }
];

const output = items.filter(({filters}) => {
  // Check if every filter has a value that is present in selectedFilters
  const isMatch = Object.entries(filters).every(([key, values]) => {
    
    // Check if the filter is not empty
    if(selectedFilters[key] && selectedFilters[key].length > 0) {
    // Check current filter values against product filter values
      return selectedFilters[key].some(value => values.includes(value));
    }
    
    return true;
  });

  return isMatch;
})

console.log(output);

6 Comments

Solution works! But this modification removes one important feature of the search: If user did not select any color, type, size e.g. const selectedFilters = { color: [], type: [], size: [] }; it was bringing all the results. How can I make the necessary change for this @theblackgigant
E.g. I want to search for orange and red ones, regardless their type and size features, so it is: const selectedFilters = { color: ["Orange", "Red"], type: [], size: [] }; and the result comes empty. Just wanted to give an example @theblackgigant
@mustafau. This can be done with a simple if statement. In my snippet above i added the check for an empty array. If it's not empty i check if the value is inside the filter.
Works here perfectly but when I try in my much more complex array which its data was converted from a json file, I get Uncaught "TypeError: Cannot read property 'length' of undefined" error now, trying to understand the reason @theblackgigant
This probably happens because the key doesn't exist on the selectedFilters, we can add this to the if statement (which i did) to check for it. This is just a theory since i don't have access to the json file that you're providing @mustafau.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.