How to fiter array with an array - javascript

I have a list of objects like this.
results = [
{ id: 1,
status : "Active"
// Some other fields
tags : [{val: 'IGM', color: 'light-success' },
{val: 'Gated Out', color: 'light-primary' },
]
},
// ...
]
now I want to filter objects in relation to tags,
the input to filter the list is also in the form of an array using multi-select input.
like
[{value: 'Gated Out', label: 'GATED OUT'}, .. ]
I'm able to filter data of other fields but not the tags because other fields are in strings and tags are an Array.
But now How can I modify this to work with the array as well.
I'm using that approach;
const handleTagsFilter = (value) => {
let updatedData = []
const dataToFilter = () => {
if (
status.length ||
custom_tags.length
) {
return filteredData
} else {
return results
}
}
setCustomTags(value)
if (value.length) {
updatedData = dataToFilter().filter((item) => {
const startsWith = item.status.toLowerCase().startsWith(value.toLowerCase())
const includes = item.status.toLowerCase().includes(value.toLowerCase())
if (startsWith) {
return startsWith
} else if (!startsWith && includes) {
return includes
} else return null
})
setFilteredData([...updatedData])
setCustomTags(value)
}
}
That function works with filtering strings like we have the status field to Active than this work, But I'm not sure how I can modify it to work with the array as well.

Maybe something like:
let search_str = 'abc'.toLowerCase();
let filtered_results = results
.map(v => v.tags.filter(_v => _v.val.toLowerCase().includes(search_str)))
.filter(v => v.length)
.reduce((a, b) => a.concat(...b), [])

Related

I need to search data in an array according to filter and set start form index of 1

I need to search data in an array according to filter so I am able to do it I am searching data according to name and id but in the backend in index 0 I am not getting and name and id only getting one number so when I search data I am getting an undefined error so what my task is I need to set filter to start searching from index 1 mean ignore index 0
coming data example from a backend
[
2,
{
search_id: "10000107",
name: "dev name",
},
{
search_id: "10000106",
name: "alberto",
},
]
function handleSearch(term) {
const dummy = props.new_alert_list.filter((item) =>
item.name.toLowerCase().includes(term)
);
const dummy1 = props.new_alert_list.filter((item) =>
item.search_id.includes(term)
);
const no = parseInt(term);
if (isNaN(no)) setItems(dummy);
else setItems(dummy1);
}
Yes you could start from element at index 1, but I think you could also use hasOwnProperty. Something like:
let data = [
2,
{
id: "10000107",
name: "dev name",
},
{
id: "10000106",
name: "alberto",
},
]
data.filter(x => {
if (x.hasOwnProperty("id") && x.hasOwnProperty("name")) {
console.log(x.id, " ", x.name); // if object in array has id and name properties then do stuff...
}
});
.filter() has a second parameter "index", which you can use.
For example:
[1,2,3].filter((element, index) => { return index != 0 });
Source: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
You can pass another parameter index in the filter()
function handleSearch(term) {
const dummy = props.new_alert_list.filter((item, index) =>
index !== 0 ? item.name.toLowerCase().includes(term) : false
);
const dummy1 = props.new_alert_list.filter((item, index) =>
index !== 0 ? item.search_id.includes(term) : false
);
const no = parseInt(term);
if (isNaN(no)) setItems(dummy);
else setItems(dummy1);
Slice the array from index 1 to create a new array to filter from.
function handleSearch(term) {
const dummy = props.new_alert_list.slice(1).filter((item) =>
item.name.toLowerCase().includes(term)
);
const dummy1 = props.new_alert_list.slice(1).filter((item) =>
item.search_id.includes(term)
);
const no = parseInt(term);
if (isNaN(no)) setItems(dummy);
else setItems(dummy1);
}
Alternatively you could access the current index being iterated (the second parameter to filter) and add a condition for non-zero (i.e. truthy) indices.
function handleSearch(term) {
const dummy = props.new_alert_list.filter((item, index) =>
index && item.name.toLowerCase().includes(term)
);
const dummy1 = props.new_alert_list.filter((item, index) =>
index && item.search_id.includes(term)
);
const no = parseInt(term);
if (isNaN(no)) setItems(dummy);
else setItems(dummy1);
}
If the first element is irrelevant in the frontend UI then perhaps you should clear it out before sending in props, or better yet, remove it before saving it into any local component state.
You can just sniff out if the item in the iterator has the properties. If so, then filter
function handleSearch(term) {
setItems(props.new_alert_list
.filter(item =>
(typeof item === "object" &&
(item.hasOwnProperty('name') && item.name.toLowerCase().includes(term.toLowerCase()))
||
(item.hasOwnProperty('search_id') && item.search_id.includes(term))
));
}
let data = [
2, {
search_id: "10000107",
name: "dev name",
},
{
search_id: "10000106",
name: "alberto",
},
{},
{
name: "Joe"
}
]
let term = 'alberto';
let filtered = data.filter(item => (typeof item === "object" && (item.hasOwnProperty('name') && item.name.toLowerCase().includes(term.toLowerCase())) || (item.hasOwnProperty('search_id') && item.search_id.includes(term))));
console.log(filtered);

How to filter an array of items if item's property contains text

I have an array of AppItems. Each app item has a property that is an array of ProfileItems. I want to filter my AppItems array based on which AppItem's have a Profile who's name contains my search text. It should return True on the first profile that contains the search text.
The problem is I'm for-looping through the profile items within a foreach within the filter function, which I don't think it likes. I have no idea how to do this.
export interface AppState {
appItems: AppItem[];
}
export interface AppItem {
profiles: ProfileItem[];
...
}
export interface ProfileItem {
name: string;
...
}
appItemsFiltered(state) {
return state.appItems
.filter((item: AppItem) => {
if (!state.filters.searchQuery) return true;
item.profiles.forEach(function (profile, index) {
const name = profile.name.toLowerCase()
const text = state.filters.searchQuery?.toLowerCase();
const result = name.indexOf(text)
if (result !== -1) return true;
})
return false
};
}
If the arrays is:
const array = [
{profiles: [{name: 'name 10'}, {name: 'name 11'}]},
{profiles: [{name: 'name 20'}, {name: 'name 21'}]},
// ...
];
Do filter like:
const filterText = 'name 21';
const result = array.filter(x => x.profiles.some(x => x.name === filterText));
result will be an array of matches.
const hasFound = result.length > 0;
When you return in a forEach it is the same as using continue in a for loop; it just moves on to the next iteration in the loop.
If you are looking for "at least one result is true" then consider using Array.prototype.some, which returns a boolean based on whether a single result in the array resolves as true.
Here's my attempt at re-writing your code with that solution, though with the data provided I can't do any better than this:
appItemsFiltered(state) {
return state.appItems
.filter((item: AppItem) => {
if (!state.filters.searchQuery) return true;
return item.profiles.some(function (profile, index) {
const name = profile.name.toLowerCase()
const text = state.filters.searchQuery?.toLowerCase();
const result = name.indexOf(text)
if (result !== -1) return true;
})
};
}
I wrote a snippet that introduces two approaches:
join() the strings & search in the resulting string
use some() with find()
const AppState = {
appItems: [{
profileItem: ['1.1', '1.2', '1.3'],
},
{
profileItem: ['2.1', '2.2', '2.3'],
},
{
profileItem: ['3.1', '3.2', '3.3'],
}
]
}
// creating an HTML representation of the list
const itemHtml = (item) => {
return `<li>${ item }</li>`
}
const profileItemsHtml = (profileItems) => {
return profileItems.map(item => itemHtml(item)).join('')
}
const createList = (appItems) => {
return appItems.reduce((a, {
profileItem
}) => {
a = [...a, ...profileItem]
return a
}, [])
}
// putting out the HTML
const updateListContainer = (container, list) => {
container.innerHTML = profileItemsHtml(list)
}
// the full list
const fullList = createList(AppState.appItems)
const fullListContainer = document.getElementById('full-list')
updateListContainer(fullListContainer, fullList)
// initiating the filtered list
let filteredList1 = fullList
const filteredListContainer1 = document.getElementById('filtered-list-1')
updateListContainer(filteredListContainer1, filteredList1)
let filteredList2 = fullList
const filteredListContainer2 = document.getElementById('filtered-list-2')
updateListContainer(filteredListContainer2, filteredList2)
// setting up filtering on input field input event
const filterInput = document.getElementById('filter-input')
filterInput.addEventListener('input', function(e) {
// FILTER 1: use join()
// if the list is made up of only strings, then you
// could join them & search the whole string at once
// might yield errors in edge cases, but mostly it
// should be correct, I think
// edge case example: 22 (you can try it)
const filtered1 = AppState.appItems.find(({
profileItem
}) => profileItem.join('').includes(e.target.value))
if (filtered1 && e.target.value) {
updateListContainer(filteredListContainer1, filtered1.profileItem)
} else {
updateListContainer(filteredListContainer1, fullList)
}
// FILTER 2: use SOME
const filtered2 = AppState.appItems.find(({
profileItem
}) => profileItem.some(item => item.includes(e.target.value)))
if (filtered2 && e.target.value) {
updateListContainer(filteredListContainer2, filtered2.profileItem)
} else {
updateListContainer(filteredListContainer2, fullList)
}
})
.list-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
<div>
<label for="filter-input">
Filter:
<input type="text" id="filter-input" />
</label>
<hr>
<div class="list-container">
<div>
FULL LIST:
<ul id="full-list"></ul>
</div>
<div>
FILTERED WITH JOIN:
<ul id="filtered-list-1"></ul>
</div>
<div>
FILTERED WITH SOME:
<ul id="filtered-list-2"></ul>
</div>
</div>
</div>

Checking and displaying values of array compared to another array in React

One data set is an object of arrays of ids and another is an object of arrays of ids and names. What I'd like to do is check if the ids from the first data exist in the second data set and if they do then display the names.
This is what is being called by the component, which works correctly:
<td>Genre</td>
<td>{this.matchGenres(this.props.movie.genre_ids, this.props.genres)}</td>
And this is the function that I can't get to work:
matchGenres = (genres, genreList) => {
genres.forEach((genre) => {
genreList.filter((list) => {
return list.id === genre;
}).map((newList) => {
return newList.name;
});
});
}
It looks like the operation performs correctly and returns the right names when I console.log it! But! its not showing up in the component on render.
const genres = [{
id: 1,
name: "Jazz Music"
}, {
id: 2,
name: "Something"
}];
const genreList = [1, 10, 100];
matchGenres = (genres, genreList) => genres
.filter(genre => genreList.includes(genre.id))
.map(genre => genre.name);
const matchedGenres = matchGenres(genres, genreList);
console.log(matchedGenres);
But! its not showing up in the component on render.
Its because your function doesn't return anything. You return inside filter and map and your function does not return anything. Also note that forEach always return undefined
You just need a minor change. Try this
let genres = ["1", "2", "3"];
let genreList = [{
id: "2",
name: "Two"
}, {
id: "32",
name: "Three"
}]
matchGenres = (genres, genreList) => {
return genreList.filter((list) => {
// findIndex return array index if found else return -1 if not found
return genres.findIndex(genere => genere === list.id) > -1;
}).map(list => list.name);
}
console.log(matchGenres(genres, genreList));
This is the solution that ended up working:
if (genreList.length !== 0) {
return genres.map(genre => genreList.find(list => list.id === genre)).map((newList) => newList.name) + ',';
}
For some reason the value of GenreList, which is an array, was showing up as empty for the first couple times the function is call. Thats another problem I'll have to look at but the if statement solves for it for the time being.

Remove object items from array of objects and group in node js

I have an array of objects as an input.
var val = [{matnr :'0001',type:'Z0001',price:12.3,location:'Afr'},{matnr :'0001',type:'Z0002',price:12.2,location:'US'},
,{matnr :'0002',type:'Z0003',price:11.2,location:'EU'}]
I need to remove location from each object and group by material.
val = [{
matnr:0001
types :[{type:'Z001',price:12.3},{type:'Z001',price:12.2}]
},
{
matnr:0002
types :[{type:'Z003',price:12.3}]
}
I tried to delete an object from an array and did a group by but seems to be not working. Could you please help
val.forEach((values)=>
Object.keys(values).forEach(function (item) {
if (item !='matnr'||item !='type' || item != price){
delete values[item];
};
})
var grouped = _.groupBy(val, function(val) {
return val.matnr;
});
You can use .reduce() with destructuring to remove the location property and group attributes by matnr by creating an object, where each key is matnr, and each value is an accumulation of properties for that given matnr like so:
const arr = [{matnr:"0001",type:"Z0001",price:12.3,location:"Afr"},{matnr:"0001",type:"Z0002",price:12.2,location:"US"},{matnr:"0002",type:"Z0003",price:11.2,location:"EU"}];
const res = Object.values(arr.reduce((acc, {matnr, type, price}) => {
const {types = []} = acc[matnr] || {};
acc[matnr] = {matnr, types: [...types, {type, price}]};
return acc;
}, Object.create(null)));
console.log(res);
.as-console-wrapper {max-height: 100% !important;}
you can do this without using loadash simply by using the higher-order functions
const cleaned = removeFromList(list).key('location')
const grouped = group(list).by('matnr')
function removeFromList(arr) {
return {
key: key => arr.map(item => {
if (!item[key]) return item
delete item[key]
return item
})
}
}
function group(arr) {
return {
by: groupKey => {
const groupsObj = arr.reduce((groups, item) => {
const groupKeyValue = item[groupKey]
if(!groupKeyValue) return groups
if (!groups[groupKeyValue]){
groups[groupKeyValue] = [item]
return groups
}
groups[groupKeyValue] = [...groups[groupKeyValue], item]
return groups
}, {});
return groupsObj
}
}
}
Note We Prefer the object structures for performance and easy access as developers, so on the group by function we return the grouped object with the materials values as keys and the matches as their values.
Like :
{
0001 : [{type:'Z001',price:12.3},{type:'Z001',price:12.2}]
0002 : [{type:'Z003',price:12.3}]
}
example available here on repl.it

Simpler method of filtering array of object of array of objects

I'm trying to filter an array of objects which has objects of array of objects inside. For example, an object in an array would look like this.
list=[...,
{
"types": [
{
"slot": 2,
"type":
{
"url": "http://pokeapi.co/api/v2/type/4/",
"name": "poison"
}
},
{
"slot": 1,
"type":
{
"url": "http://pokeapi.co/api/v2/type/12/",
"name": "grass"
}
}
],
"name": 'bulbasaur'
},...
]
I'm currently filtering the list by its name and the types of objects like this (the this.props.search.search being a string, and the example being an example list of strings that will be adjusted):
let filtered = this.props.pokemons.filter((pokemon)=>{
return pokemon.name.toLowerCase().indexOf(this.props.search.search.toLocaleLowerCase())!==-1;
})
let example = ['fire', 'ice', 'water'];
let filteredx= filtered.filter((pokemon)=>{
return pokemon.types.filter((type)=>{
return example.indexOf(type.type.name)!==-1
}).length>0
})
Is there a method of combining all the filters into one instead of calling
array.filter(...).filter(...)
As in the future, if more filters are added, I'm afraid that it's going to end up looking like
array.filter(...).filter(...).filter(...).filter(...).filter(...)
Any help would be appreciated.
You can combine the two conditions with an &&:
let filteredx = this.props.pokemons.filter(pokemon =>
pokemon.name.toLowerCase().includes(this.props.search.search.toLocaleLowerCase())
&& pokemon.types.some(type => example.includes(type.type.name))
)
Note you can use includes and some in your conditions, and use the expression syntax in your arrow functions (without braces nor return).
You can add more conditions with additional && operators. Make sure to put them in such order that the most simple conditions (that require least work) come first.
If the array is small and perfomance not an issue;
const arryOfPokemons = [{name: 'name1', type: 'type1'}];
function name(pokemon) { return pokemon.name !== 'name' }
function type(pokemon) { return pokemon.type !== 'type' }
const result = [name, type].reduce((result, filterFunc) => result.filter(filterFunc), arryOfPokemons);
otherwise you can try to combine the conditions into the same filter function.
Instead of filtering multiple times, you can combine all the filter conditions in one filter.
Instead of doing this...
let filtered1 = toFilter.filter((element) => {
return condition1;
});
let filtered2 = filtered1.filter((element) => {
return condition2;
});
...
let filteredN = filteredN_1.filter((element) => {
return conditionN;
});
... you can combine the conditions in a single filter:
let filtered = toFilter.filter((element) => {
return condition1 && condition2 && ... && conditionN;
});
If one of the conditions is very long, you can easily abstract it in a separate function. This also makes the code more readable and maintainable.
let filtered = toFilter.filter((element) => {
const condition1 = computeCondition1(arg1, arg2);
const condition2 = computeCondition2(arg1);
...
const condition3 = computeCondition3(arg2, arg3, arg4);
return condition1 && condition2 && ... && conditionN;
});
You could define an object which contains a property for every first level property of your pokemon list you want to test. The value would be a predicate with the "test logic" for this property.
const pokemons = [
{"name":"poke1","types":[{"type":{"name":"poison"}},{"type":{"name":"grass"}}]},
{"name":"poke2","types":[{"type":{"name":"fire"}},{"type":{"name":"grass"}}]},
{"name":"poke3","types":[{"type":{"name":"ice"}},{"type":{"name":"water"}}]},
{"name":"poke4","types":[{"type":{"name":"ice"}},{"type":{"name":"grass"}}]}
];
const filterOptions = {
name: (name) => {
return ["poke1", "poke5"].some(x => x === name);
},
types: (types) => {
return ["ice", "water"].some(t => types.some(x => t === x.type.name));
}
};
function filterList(list, options) {
return list.filter(pokemon => {
return Object.keys(options)
.some(key => {
if (key in pokemon) {
return filterOptions[key](pokemon[key]);
}
});
});
}
const filtered = filterList(pokemons, filterOptions);
filtered.forEach(p => console.log(JSON.stringify(p)));

Categories