filter in react with multiple conditions - javascript

I am applying following filter on a table with the help if useEffect
useEffect(()=>{
let updated = data.filter(function(e){
if ((e.country === aaa|| e.country === undefined) && (e.age === bbb|| e.age === undefined)){
return true
}
})
setRows(updated)
},[aaa, bbb])
it works fine when "Select" for both the filters are selected if one if them is undefined then it does not show any data

I prefer making multiple functions when filtering criteria differs. It's more readable for other developers and easier to modify and maintain.
I don't know about the actual end result since I don't know your exact use case. But this structure of code should give you a hint on how to approach that:
const byCountry = (country) => (item) => {
return item.country === country || item.country === undefined;
};
const byAge = (age) => (item) => {
return item.age === age || item.age === undefined;
};
const getUpdated = (data, country, age) => {
return data
.filter(byCountry(country))
.filter(byAge(age));
};
const data = [{
country: 'Finland',
age: '42',
}, {
country: 'Sweden',
age: '39',
}, {
country: 'Sweden',
}];
console.log(getUpdated(data, 'Finland', '42'));
console.log(getUpdated(data, 'Sweden', '39'));

Related

The best way to filter down a dataSet with javascript

The idea was to query a dataset with querystring params. I only want the "records" to match only what was queried.
Dataset
{
1111:
{
Category: "Education"
Role: "Analyst"
}
2222:
{
Category: "Communications and Media"
Role: "Analyst"
}
3333:
{
Category: "Public Sector"
Role: "Something else"
}
4444:
{
Category: "Public Sector"
Role: "Something else"
}
...
}
[[Prototype]]: Object
I'm sending in qString
Category: (2) ['Communications and Media', 'Education']
Role: ['Analyst']
length: 0
[[Prototype]]: Array(0)
I'd like to loop over that and filter/reduce so I only have records that match. Sort of an and instead of an or.
dataSet is an Object of objects. Thoughts? Thanks in advance.
export const Filtered = (qStrings, dataSet) => {
const filtered = [];
Object.entries(qStrings).forEach(([field]) => {
qStrings[field].forEach((value) => {
filtered.push(
..._.filter(dataSet, (sess) => {
if (sess[field] && sess[field].toString() === value.toString()) {
return sess;
}
})
);
});
});
return _.uniq(filtered);
};
geez, I figured it out with a colleague who's way smarter than me wink Jess!
export const Filtered = (qStrings, dataSet) => {
let filtered = [];
Object.entries(qStrings).forEach(([field], idx) => {
let source = filtered;
if (idx === 0) {
source = dataSet;
}
filtered = _.filter(source, (sess) => {
return sess[field] && sess[field].includes(qStrings[field]);
});
});
return _.uniq(filtered);
};
Now to clean this up.
Not sure if this solves your problem exactly, but you can apply this logic without mutation for a much cleaner function.
export const matches = (qStrings, dataSet) =>
Object.entries(dataSet).reduce((acc, [key, value]) =>
Object.entries(value).every(([rKey, rValue]) => qStrings[rKey]?.includes(rValue))
? { ...acc, [key]: value }
: acc,
{});
This will return records 1111 and 2222 because they match one of the categories and the role in qStrings.

search array of objects.shortName and also nested array of objects

I'm trying to filter a large array of objects that also have nested values.
I need to match shortName OR description OR isoCode. Some of the items may have 20+ countries but most have 1 to 5.
{
countries: Array(1)
0:
description: "United Kingdom"
isoCode: "GB"
1:
description: "Italy"
isoCode: "IT"
shortName: "AIB (NI)"
},
// * 2000
I've tried building on this with limited success.
methods: {
filterInstitutions: function (items: any, event: any): void {
console.log(items, event.target.value);
if (event === '') {
newFunction(items);
} else {
this.listedInstitutions = items.filter((item: any) => {
return item.shortName.toLowerCase().includes(event.target.value.toLowerCase());
})
}
},
},
I am building this in Vue (typescript) but understand its as much of a JS question than a Vue one.
Any suggestions welcome.
You will need to add in tests for the description and isoCode of your countries property to your filter function.
One way would be to use the Array's some() method which will test if any of the array element's match any test you setup in the callback. It will then return true if any do match and false if not.
let testValue = event.target.value.toLowerCase();
this.listedInstitutions = items.filter((item: any) => {
//test the shortName
let matchesShortName = item.shortName.toLowerCase().includes(testValue);
//loop through countries and see if any description or isoCode match
let matchesDescIsoCode = item.countries.some((item:any) => {
let desc = item.description.toLowerCase();
let code = item.isoCode.toLowerCase();
return desc.includes(testValue) || code.includes(testValue);
});
return matchesShortName || matchesDescIsoCode;
})
Example
function doFilter(event, items) {
let testValue = event.target.value.toLowerCase();
let listedInstitutions = items.filter((item) => {
let matchesShortName = item.shortName.toLowerCase().includes(testValue);
let matchesDescIsoCode = item.countries.some((item) => {
let desc = item.description.toLowerCase();
let code = item.isoCode.toLowerCase();
return desc.includes(testValue) || code.includes(testValue);
});
return matchesShortName || matchesDescIsoCode;
});
console.clear();
console.log("Filtered List");
console.log(listedInstitutions);
}
let someItems = [{
countries: [{
description: "United Kingdom",
isoCode: "GB"
},
{
description: "Italy",
isoCode: "IT"
}
],
shortName: "AIB (NI)"
}, {
countries: [{
description: "United States",
isoCode: "US"
},
{
description: "Italy",
isoCode: "IT"
}
],
shortName: "ABC (DE)"
}]
<input type="text" oninput="doFilter(event,someItems)">
You could create an array of properties to search on within your object(s) and determine if any matches exist.
this.listedInstitutions = items.filter((item: any) => {
return item.countries.filter(
country => ['description', 'isoCode', 'shortName'].filter(prop => item.prop && item.prop.toLowerCase().includes(event.target.value.toLowerCase()).length > 0
).length > 0
})
Using filter() and some():
function filterInstitutions (items, {target: {value}}) {
const isMatch = text => text.toLowerCase().includes(value.toLowerCase());
return items.filter(({shortName, countries}) => (
isMatch(shortName) || countries.some(({description, isoCode}) => (
isMatch(description) || isMatch(isoCode)
))
));
};

Angular filter by dynamic columns

I have filter function which is working just fine when I use static column name in it like:
this.listOfData = this.listOfData.filter((item: DataItem) =>
item.name.toLowerCase().indexOf(newValue.toLowerCase()) !== -1
);
PS: item.name
But I need to search in every column of the item, how can I do that?
PS: name should be dynamic.
My ListofData has this columns:
listOfData({
id:
ticket_number:
status_name:
name: // currently my function is set to this value only.
created_by_full_name:
receive_time:
response_time:
resolution_time:
})
Update
based on Allabakash answer I have final code below which returning lots of typescript error:
ngOnInit(): void {
// this listens to the input value from the service and does something on change.
this.globalSearchService.searchTerm.subscribe((newValue: string) => {
// this is where you would apply your existing filtering.
this.searchTerm = newValue;
if(newValue != null) {
this.visible = false
this.listOfData = this.listOfData.filter((item: DataItem) =>
let keys = Object.keys(item);
for (let key of keys) {
if (typeof item[key] === 'string' &&
item[key].toLowerCase().indexOf(newValue.toLowerCase()) !== -1) {
return true;
}
}
return false;
);
}
});
}
If you wanted to search on all properties dynamically, you can try something like this.
this.listOfData = this.listOfData.filter((item: DataItem) => {
let keys = Object.keys(item);
for (let key of keys) {
if (typeof item[key] === 'string' &&
item[key].toLowerCase().indexOf(newValue.toLowerCase()) !== -1) {
return true;
}
}
return false;
}
);
You can use ES6 for a one liner solution. Below is a sample using vanilla Javascript
const initialListOfData = [
{ name: 'Peter', surname: 'John'},
{ name: 'Judas', surname: 'James'},
{ name: 'Paul', surname: 'Peter'},
{ name: 'Petrover', surname: 'Junior'}
]
const searchItem = 'pet'
const listOfData = initialListOfData.filter(
item => Object.keys(item).some(prop =>
(new RegExp(searchItem.toLowerCase())).test(item[prop].toLowerCase())
))
console.log(listOfData)
For your Problem using typescript this will be
this.listOfData = initialListOfData.filter(
(item: any) => Object.keys(item).some((prop: any) =>
(new RegExp(newValue.toLowerCase())).test(item[prop].toLowerCase())
))

How do I eliminate all these if-else

I have written this code to filter a "books" array, depending on the author or genre have been given as a parameter:-
//This code works perfectly fine as is
allBooks: (root, args) => {
if (args.author === undefined && args.genre === undefined) {
return books
} else if (args.author === undefined) {
return books.filter((book) => book.genres.includes(args.genre))
} else if (args.genre === undefined) {
return books.filter((book) => book.author === args.author)
} else {
const booksFilter = books.filter((book) => book.author === args.author)
return booksFilter.filter((book) => book.genres.includes(args.genre))
}
}
I believe there must be some way to write this more "professionally" without using all these if-else. So if anyone knows a better way, I'll appreciate it.
[Edited]
Thanks to all, I decided to go with ghostkraviz solution, code looks like this now:
allBooks: (root, args) => {
return books.filter((book) => {
const filteredGenres = book.genres.filter((genre) =>
genre.includes(args.genre || "")
);
return book.author.includes(args.author || "") && filteredGenres.length > 0;
})
}
You could take an array for filtering with key/value pairs, like
filters = [
['author', 'eliot'],
['genre', 'fiction']
]
and an object for storing special type of searching, like
methods = {
genre: 'includes'
}
Together, you get the following function
result = books.filter(book => filter.every(([key, value]) => key in methods
? book[key][methods[key]](value)
: book[key] === value
));
Because of Array#every's return value of true for empty arrays, you need no further action to get all books.
since String.prototype.includes actually checks if a string maybe found within another string (the parameter) MDN String.prototype.includes. that means, for undefined args you could default it to empty string. Empty strings will return true if checked with .includes from any string.
you only check for 2 args which are the author & genre.
here's the example:
const books = [
{author: "A", genres: ["Horror", "romance"]},
{author: "B", genres: ["romance"]},
{author: "X", genres: ["science"]},
{author: "C", genres: ["science", "Horror"]}
];
const allBooks = (root, args) => {
return books.filter(book => {
const filteredGenres = book.genres.filter(genre =>
genre.includes(args.genre || "")
);
return book.author.includes(args.author || "") && filteredGenres.length > 0;
});
};
console.log('filtered Horror = ', allBooks({}, {genre: 'Horror'}));
console.log('filtered A and Horror = ', allBooks({}, {author: 'A', genre: 'Horror'}));
console.log('filtered romance = ', allBooks({}, {genre: 'romance'}));
// for all books result
console.log('filtered romance = ', allBooks({}, {}));
// for an author result
console.log('filtered author(A) = ', allBooks({}, {author:"A"}));
I don't know if the version below is written more "professionally" as it looks messy, but it is a single line, and does not use if-else.
books = [{
author: "a",
genres: ["a"]},
{author: "a",
genres: ["a", "b"]},
{author: "b",
genres: ["c", "b"]
}];
allBooks = (root, args) => {
return (!args.author && !args.genre) ? books : (!args.author) ?
books.filter((book) => book.genres.includes(args.genre)):(!args.genre)?
books.filter((book) => book.author === args.author) :
(books.filter((book) => book.author === args.author)).filter((book) =>
book.genres.includes(args.genre));
}
console.log(allBooks("",{genre: "b"}));
//console:
//0: {author: "a", genres: Array(2)}
//1: {author: "b", genres: Array(2)}
I like to use if-else, as it's more readable. We can also eliminate the else-ifs since each of the if statement has a return.
This is just a simplified version of your existing codes without the else-ifs and curly brackets
allBooks: (root, args) => {
const { author, genre } = args;
if (!author && !genre) return books;
if (!author) return books.filter((book) => book.genres.includes(genre))
if (!genre) return books.filter((book) => book.author === author);
return books.filter((book) => book.author === author).filter((book) => book.genres.includes(genre))
}
Sometimes it's not about writing codes professionally.
It's about writing codes that you can easily understand.

Console output for function with multiple optional arguments

Console output for function with multiple arguments
I have this class method:
searchForProduct({productName, manufacturer, seller}, itemsPerPage = 20, onlyAvailable = true) {
console.log(Searching for...) // Here's what is my question about
//do stuff
}
How can I print out all arguments that I'm passing to the method?
What a want to achieve is:
searchForProduct({productName: laptop});
// Output:
"Searching for productName: 'laptop'"
// or
searchForProduct({productName: "laptop", manufacturer: "Dell"});
// Output:
"Searching for productName: 'laptop', manufacturer: 'Dell'"
And so on...
Also (if it's possible with any approach that will be proposed) I don't want to print out default itemsPerPage and onlyAvailable even if it will be passed to the method.
UPD:
Wow, I was not expecting this amount of such neat approaches in the answers.
However I should admit that I'm not allowed to change this function. Basically I just need to add this output form my personal needs since changing this method will "break everything".
I apologize for time that you took to propose passing object instead of destructed arguments. I'll upvote your answers anyway
UPD2:
I tried a couple of suggestion but still didn't achieved the perfect result:
searchForProduct({productName, manufacturer, seller}, itemsPerPage = 20, onlyAvailable = true) {
function buildString({firstArgument, secondArgument, thirdArgument}) {
return {
productName: (firstArgument !== undefined) ? firstArgument : "",
manufacturer: (secondArgument !== undefined) ? secondArgument : "",
seller: (thirdArgument !== undefined) ? thirdArgument : ""
}
}
const searchString = buildString({productName, manufacturer, seller})
const displayStr = Object.entries(searchString)
.map(([key, val]) => key + ': ' + val)
.join(', ');
console.log('Searching for', displayStr);
//do stuff
}
The buildString function is super ugly but it works without changing the original function.
However I have faced an issue when not all arguments are passed
It will look like:
"Searching for productName: "laptop", manufacturer: "Dell", seller: undefined
I tried:
function buildString({firstArgument, secondArgument, thirdArgument}) {
return {
productName: (firstArgument !== undefined) ? firstArgument : delete productName,
manufacturer: (secondArgument !== undefined) ? secondArgument : delete manufacturer,
seller: (thirdArgument !== undefined) ? thirdArgument : delete seller
}
}
But this gives me:
"Searching for productName: "laptop", manufacturer: "Dell", seller: true
According to MDN:
When trying to delete a property that does not exist, true is
returned
However property does exist - it's value does not. So what I tried to above is to delete the object key if it does not have a value.
Currently no success
I wouldn't destructure the first argument - instead, stringify it or iterate over its entries to extract its keys and values:
const searchForProduct = (obj) => {
console.log('Searching for', JSON.stringify(obj));
};
searchForProduct({productName: 'laptop'});
searchForProduct({productName: "laptop", manufacturer: "Dell"});
const searchForProduct = (obj) => {
const displayStr = Object.entries(obj)
.map(([key, val]) => key + ': ' + val)
.join(', ');
console.log('Searching for', displayStr);
};
searchForProduct({productName: 'laptop'});
searchForProduct({productName: "laptop", manufacturer: "Dell"});
You can also use a wrapper function that does the same thing:
const searchForProductWrapper = (obj, itemsPerPage = 20, onlyAvailable = true) => {
const displayStr = Object.entries(obj)
.map(([key, val]) => key + ': ' + val)
.join(', ');
console.log('Searching for', displayStr);
searchForProduct(obj, itemsPerPage, onlyAvailable);
};
const searchForProduct = (obj, itemsPerPage, onlyAvailable) => {
console.log('true searchForProduct', obj, itemsPerPage, onlyAvailable);
};
searchForProductWrapper({productName: 'laptop'}, 10);
searchForProductWrapper({productName: "laptop", manufacturer: "Dell"});
How can I print out all arguments that I'm passing to the method?
Its more like you would like to see object properties of first argument
function searchForProduct(data, itemsPerPage = 20, onlyAvailable = true) {
console.log(`Searching for ${JSON.stringify(Object.entries(data))}`)
}
criteria = {
productName: '',
manufacturer: ''
}
searchForProduct(criteria)

Categories