I am still trying to wrap my head around filtering an array of objects in Javascript; spent a couple of hours figuring this out.
I have this array of objects being returned to me from my backend. Inside each object, there is a key that has an array of objects as its value. AND inside this array, there's another array of objects, with another array... Yeah it gets pretty confusing.
So something like this:
{
menuEntities: Array(1)
0:
categoryEntities: Array(2)
0:
categoryId: 1
categoryName: "Main"
menuItemEntities: Array(1)
0: {customisableItemEntities: Array(0), description: "Full of delicious beef", enabled: true, foodItemEntities: Array(0), imagePath: "", menuItemName: "Burger"}
length: 1
__proto__: Array(0)
__proto__: Object
1: {categoryId: 2, categoryName: "Drinks", menuItemEntities: Array(1)}
length: 2
__proto__: Array(0)
isSelected: true
menuId: 1
menuName: "Menu 1"
__proto__: Object
length: 1
__proto__: Array(0)
}
What I wanted to do was build a filter function to return true if my input text includes a particular menuItemName. For example, type in burger into my input field and all the restaurants that contain burger would show up in my search results.
I came across this post on Stack Overflow that suggests to use some
After a bit of tinkering, I got this:
this.sortedRestaurants = this.sortedRestaurants.filter(function(
restaurant
) {
if (_.isEmpty(restaurant.menuEntities) == false) {
return restaurant.menuEntities[0].categoryEntities.some(category =>
category.menuItemEntities.some(menuItemEntity =>
menuItemEntity.menuItemName
.toLowerCase()
.includes(val.toLowerCase())
)
);
}
});
And that works for the current use case!
But I don't understand why when I tried forEach initially, this didn't work:
this.sortedRestaurants = this.sortedRestaurants.filter(function(
restaurant
) {
if (_.isEmpty(restaurant.menuEntities) == false) {
return restaurant.menuEntities[0].categoryEntities.forEach(e => {
e.menuItemEntities.forEach(menuItemEntity => {
menuItemEntity.menuItemName
.toLowerCase()
.includes(val.toLowerCase());
});
});
}
});
To me, wouldn't includes still return a true or false value for the case of the forEach function..?
How would you guys write this function better too?
Top comments (16)
includes
returns a boolean value butforEach
does not (or rather, it returnsundefined
). So the result of theincludes
call is simply thrown away if you useforEach
instead ofsome
.Two obvious improvements to the working method:
restaurant.menuEntities
withsome
as well, if it's possible that the result you're looking for is not in the very first onereduce
instead offilter
; if you search, there have been numerous guides posted hereAdditionally, if you're retrieving the restaurant & menu records from a relational database you could short-circuit the whole thing by filtering in your query instead of in application logic.
Hahhaa for the menuEntities yeah I'll definitely have to use .some if there's more than one.. Thanks for the reminder & explanation, makes sense that the results of includes is thrown away with forEach
Just to summarize:
forEach
if you need to act on every item (display all prices)map
same, but return a new array with the new values (add 10% tax to all prices)filter
if you need to remove items (get prices under 10$)includes
if you need to know if the array contains at least one (does one prices is exactly equal to 10$)some
same, but you can use a function as assertion (does one price is under 10$)reduce
if you need to transform an array into one value (sum all prices)ps: Try to never modify a variable (except iterator).
The
reduce
accumulator doesn't have to be one value -- it's quite useful in places you'd use bothfilter
andmap
to choose some elements of an array and generate derived values, and you only have to traverse the array once.For large data set I would agree (even tho I never thought of it). But for sub 100 items, the lose in clarity isn't much worth it.
Looks like the answer to why your initial solution didn't work has been answered. I just wanted to leave a few suggestions.
Avoid using == in favor of ===. == Will attempt to type convert and unless that's what you need you can run into some false positives. If it is what you explicitly need you're better off converting and then comparing with === yourself
Instead of checking _.isEmpty(thing) == false, you can write !isEmpty(thing). It'll be easier to read when/if you or anyone else has to come back and do additional work on this code.
You're mixing two flavors of syntax with an ES5 function definition and an ES6 fat arrow function definition. It's not wrong but unless you have a good reason for it, it's probably better to stick to one again for code legibility down the line.
Don't forget filtter and find. Find gives just one result and filter gives many reults.
If I'm looping through a crowd of people to find on person.
If I'm looking for a particular group to filter through.
Hi, a filter must return boolean for every element in the array, you are not doing that. The filter will fill your result with only the elements that returned true. You should check the mdn docs for the Array.prototipe.filter, it comes with examples and very well explained.
You may also want to take a look at underscorejs.org, it's a lightweight library that makes things like this much easier and cleaner
aye i already am using underscorejs, installed lodash in my project. what would you suggest to get this done though?
I think you have gotten enough suggestions by now to 'wrap your head around it'.
@sarah_edo wrote a cool tool, Array Explorer, that I think you'd find helpful.
codepen.io/sdras/details/gogVRX
This is awesome & definitely useful! Thanks for the intro Nick and ofc thanks Sarah for making this!
github.com/mihaifm/linq and jquery grep are helpful.
Don't use
forEach
. You're expecting the end result to be an array. Use.map
,.filter
, or.reduce
with to return a new array.If it helps, here's my writeup of Array methods in JavaScript:
What to do with lists of things in JavaScript
Klemen Slavič