Javascript iterate an array with two conditions - javascript

I have this array and these values:
const levels = [
{
name: 'first level primary school'
},
{
name: 'second level primary school'
},
{
name: 'first level secondary school'
}
]
const isPrimarySchool = false
const isSecondarySchool = false
And I want to iterate the array and find if there is a string with the word 'primary' or 'secondary' and turn the value of isPrimarySchool or isSecondarySchool to true.
The method that I found to resolve this, is this one:
levels.forEach(level => {
if (
level.name.toLowerCase().includes('primary')
) {
isPrimarySchool = true
}
if (
level.name.toLowerCase().includes('secondary')
) {
this.isSecondarySchool = true
}
})
Is there a better way to resolve this, using lodash or normal javascript?

You could take a function for using with Array#some and hand over the expected value.
const
has = string => o => o.name.includes(string),
levels = [{ name: 'first level primary school' }, { name: 'second level primary school' }, { name: 'first level secondary school' }],
isPrimarySchool = levels.some(has('primary')),
isSecondarySchool = levels.some(has('second'))
console.log(isPrimarySchool);
console.log(isSecondarySchool);

I want to iterate the array and find if there is a string with the
word 'primary' or 'secondary' and turn the value of isPrimarySchool or
isSecondarySchool to true.
Using some() with includes
const levels = [
{
name: 'first level primary school'
},
{
name: 'second level primary school'
},
{
name: 'first level secondary school'
}
]
const isPrimarySchool = levels.some(x=>x.name.toLowerCase().includes('primary'))
const isSecondarySchool = levels.some(x=>x.name.toLowerCase().includes('secondary'))
console.log({isPrimarySchool,isSecondarySchool})

You could use the built-in Array.some() function and some regexp:
isPrimarySchool = levels.some(level => /primary/.exec(level.name));
isSecondarySchool = levels.some(level => /secondary/.exec(level.name));

Related

Searching through array of objects that contain arrays of objects efficiently

I am currently searching through an array of objects that contains an array of objects, the data structure looks like the following:
const category = 'general'
const field = 'rating'
const schema = {
categories: [
{
name: 'general',
label: 'General',
fields: [
{
name: 'id',
label: 'Market Street ID',
type: 'string'
},
{
name: 'rating',
label: 'Rating',
type: 'number'
}
]
},
{
name: 'location',
label: 'Location',
fields: [
{
name: 'search',
label: 'Query Parameters',
type: 'string'
}
]
}
]
}
The expected result is to search through the categories array for a given category name (in this case 'general') and then search through the fields for a given field name (in this case 'rating') and return the type associated with the field. I have achieved this by doing the following:
const type = schema.categories
.filter((cat) => cat.name === category)
.map((cat) => {
const fieldData = cat.fields.find((f) => f.name === field)
return fieldData.type
})
console.log(type[0])
I am wondering if there is a more efficient way to search through the array of objects within an array of objects, possibly with the type not being returned in an array
If you only need the first match, you can use Array#find along with optional chaining.
const category="general",field="rating",schema={categories:[{name:"general",label:"General",fields:[{name:"id",label:"Market Street ID",type:"string"},{name:"rating",label:"Rating",type:"number"}]},{name:"location",label:"Location",fields:[{name:"search",label:"Query Parameters",type:"string"}]}]};
const type = schema.categories.find(cat => cat.name === category)
?.fields.find(f => f.name === field).type;
console.log(type);

Replace all values in an object with a specific key

My goal here is to convert all type values in the array below to the types in the object that collerate to numbers.
let obj = [
{
type: 'boolean',
name: 'coolName',
description: 'First description',
required: false
},
{
type: 'subcommand',
name: 'destroy',
description: 'Destroy something',
options: [
{
type:"integer",
name:"amount",
description:"How much would you like to destroy?",
required: true
}
]
}
]
const types = {
'subcommand':1,
'subcommandgroup':2,
'string':3,
'integer':4,
'boolean':5,
'user':6,
'channel':7,
'role':8,
'mentionable':9,
'number':10,
'attachment':11
}
I've been looking for a while and cannot find a function that also iterates through the nested object, if anyone has a way to do this please let me know.
obj.map(o => {
o.type = types[o.type]
if(o.options){
o.options.map(opt => {
opt.type = types[opt.type]
})
}
return o;
})

How to fix an error when working with an array and the find method?

I have a question about working with the find method. I have a task - I need to go through the array and find a match with a specific string. But at the same time there is a condition that this string can be inside one of the objects already in its child array. I make an if construct in my function to check this when passing through the array, but it does not work out as I expected. Tell me, please, where I went wrong.
P.S. I write more correctly. If the array object "newList" has "items" , then you need to look for comparison not in the object, but in its "items" array among "role" . If "items" does not exist for the object, then we look for a match in this object among "role"
const newList = [
{
role: "role111",
title: "title1",
},
{
role: "role222",
title: "title2",
},
{
role: "role333",
title: "title3",
},
{
role: "role444",
title: "title4",
items: [{
role: "role555",
title: "title5",
}, {
role: "role666",
title: "title6",
}, {
role: "role777",
title: "title7",
},]
},
{
role: "role888",
title: "title8",
},
];
const url = "role7";
export const findAfterRefresh = (list, url) =>
list.find((item) => {
if (item.items && item.items?.length > 0) {
return item.items.find((childrenITem) => childrenITem.role.includes(url));
}
return item.role.includes(url);
});
;
findAfterRefresh(newList, url);
Your solution was close, but if you call find on newList, it can only ever return one of the elements of newList, it can't return an element from the items array of one of those elements. That plus the fact you want the role value, not the element itself, makes the find method not a good match for your current data structure (but keep reading; if you really want to use find, there's a way).
Instead, a simple loop with recursion does the job:
/*export*/ const findAfterRefresh = (list, url) => {
for (const item of list) {
if (item.role?.includes(url)) {
return item.role;
}
if (item.items?.length) {
const childRole = findAfterRefresh(item.items, url);
if (childRole) {
return childRole;
}
}
}
};
Here's a version with explanatory comments:
/*export*/ const findAfterRefresh = (list, url) => {
// Loop the given list...
for (const item of list) {
// ...check this item's role
// (Remove v-- this `?` if `role` will always be there)
if (item.role?.includes(url)) {
return item.role;
}
// If this item has child items, check them
if (item.items?.length) {
// Search child items using recursion
const childRole = findAfterRefresh(item.items, url);
if (childRole) {
// Found it, return it
return childRole;
}
// Didn't find it, keep looping
}
}
};
Live Example:
const newList = [
{
role: "role1",
title: "title1",
},
{
role: "role2",
title: "title2",
},
{
role: "role3",
title: "title3",
},
{
role: "role4",
title: "title4",
items: [
{
role: "role5",
title: "title5",
},
{
role: "role6",
title: "title6",
},
{
role: "role7plusotherstuff",
title: "title7",
},
],
},
];
/*export*/ const findAfterRefresh = (list, url) => {
// Loop the given list...
for (const item of list) {
// ...check this item's role
// (Remove v-- this `?` if `role` will always be there)
if (item.role?.includes(url)) {
return item.role;
}
// If this item has child items, check them
if (item.items?.length) {
// Search child items using recursion
const childRole = findAfterRefresh(item.items, url);
if (childRole) {
// Found it, return it
return childRole;
}
// Didn't find it, keep looping
}
}
};
console.log("Searching for 'role7'");
console.log(findAfterRefresh(newList, "role7"));
console.log("Searching for 'role2'");
console.log(findAfterRefresh(newList, "role2"));
Note: I added a bit to the role containing role7 so you could see that the code returns the full role, not just the bit in url.
But if you really want to use find, you can do it by first creating a flat array of roles:
// Creates a new array of `role` values (the array may also contain
// `undefined`, if the `role` property of any element or child element is
// `undefined`)
const flattenRoles = (list) =>
(list ?? []).flatMap((item) => [item.role, ...flattenRoles(item.items)]);
/*export*/ const findAfterRefresh = (list, url) => {
return flattenRoles(list).find((role) => role?.includes(url));
};
That code's a big shorter, but note that it creates a number of temporary arrays, and it always works its way through the full list before looking for roles, whereas the earlier version stops looking as soon as it's found a matching role. That's unlikely to be a problem if newList is of a reasonable size, but it's worth keeping in mind. (I'd probably use the earlier version, not this.)
Here's that in action:
const newList = [
{
role: "role1",
title: "title1",
},
{
role: "role2",
title: "title2",
},
{
role: "role3",
title: "title3",
},
{
role: "role4",
title: "title4",
items: [
{
role: "role5",
title: "title5",
},
{
role: "role6",
title: "title6",
},
{
role: "role7plusotherstuff",
title: "title7",
},
],
},
];
// Creates a new array of `role` values (the array may also contain
// `undefined`, if the `role` property of any element or child element is
// `undefined`)
const flattenRoles = (list) =>
(list ?? []).flatMap((item) => [item.role, ...flattenRoles(item.items)]);
/*export*/ const findAfterRefresh = (list, url) => {
return flattenRoles(list).find((role) => role?.includes(url));
};
console.log("Searching for 'role7'");
console.log(findAfterRefresh(newList, "role7"));
console.log("Searching for 'role2'");
console.log(findAfterRefresh(newList, "role2"));
In a comment you've asked:
ESLint: iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.(no-restricted-syntax)
...how much do you think eslnit is right in this case?
That's up to you. If your target environment is ES2015+, there's no need for regenerator-runtime. As far as I know, there are no major pre-ES2015+ environments (IE11 is obsolete and discontinued). But if you need to support it and want to avoid regenerator-runtime, you can replace the for-of loop with some and assigning to a closed-over variable:
const newList = [
{
role: "role1",
title: "title1",
},
{
role: "role2",
title: "title2",
},
{
role: "role3",
title: "title3",
},
{
role: "role4",
title: "title4",
items: [
{
role: "role5",
title: "title5",
},
{
role: "role6",
title: "title6",
},
{
role: "role7plusotherstuff",
title: "title7",
},
],
},
];
/*export*/ const findAfterRefresh = (list, url) => {
// Loop the given list...
let role;
list.some((item) => {
// ...check this item's role
// (Remove v-- this `?` if `role` will always be there)
if (item.role?.includes(url)) {
role = item.role;
return true;
}
// If this item has child items, check them
if (item.items?.length) {
// Search child items using recursion
const childRole = findAfterRefresh(item.items, url);
if (childRole) {
// Found it, return it
role = childRole;
return true;
}
// Didn't find it, keep looping
}
});
return role;
};
console.log("Searching for 'role7'");
console.log(findAfterRefresh(newList, "role7"));
console.log("Searching for 'role2'");
console.log(findAfterRefresh(newList, "role2"));
The return true; statements tell some that you're done, it can stop looping.
...and the error on childRole from typescript is TS7022: 'childRole' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
The TypeScript error is just because the code doesn't have type annotations, because it was a JavaScript question, not a TypeScript question. :-) If you add the appropriate type annotations, it'll be fine.
As per my understanding, You are trying to filtered out the newList with all the objects includes role7 string in role property either in main object or in the child array objects. If Yes, You have to use Array.filter() method instead of Array.find() as it will only returns the first element in the provided array that satisfies the provided testing function.
Live Demo :
const newList = [
{
role: "role111",
title: "title1",
},
{
role: "role777",
title: "title2",
},
{
role: "role333",
title: "title3",
},
{
role: "role444",
title: "title4",
items: [{
role: "role555",
title: "title5",
}, {
role: "role666",
title: "title6",
}, {
role: "role777",
title: "title7",
},]
},
{
role: "role888",
title: "title8",
},
];
const url = "role7";
const findAfterRefresh = (list, url) =>
list.filter((item) => {
return (item.role.includes(url)) ? item :
item.items = item.items?.filter((childrenITem) => childrenITem.role.includes(url));
});
console.log(findAfterRefresh(newList, url));
After reading some of the other solutions I was able to refactor my code down a little bit. I decided to convert the raw array value into a 2d array, each nested array value would hold the role and item values.
I chose to do things this way so that the output of this function can still be used for any purpose regarding the roles and titles of these items. and to create an array which would be easy to iterate through.
const url = "role6";
const findAfterRefresh = (list) =>{
let foundData = []
list.find((item) => {
foundData.push([item.role, item.title]);
if (item.items !== undefined) {
item.items.find((item) => {
foundData.push([item.role, item.title]);
})
}
});
return foundData
}
for(let v in findAfterRefresh(newList) ){
if (findAfterRefresh(newList)[v].includes(url)) {
console.log(findAfterRefresh(newList)[v].includes(url)); break
}
}
//output of findAfterRefresh: [[role1,title1],[role2,title2][... etc
//ouput of the for loop at the bottom: true...
Ok I got to it.
Edit: I didn't read the title carefully, the code at the bottom is with for instead of "find" I hope it's not a problem
const newList = [
{
role: "role1",
title: "title1",
},
{
role: "role2",
title: "title2",
},
{
role: "role3",
title: "title3",
},
{
role: "role4",
title: "title4",
items: [{
role: "role5",
title: "title5",
}, {
role: "role6",
title: "title6",
}, {
role: "role7",
title: "title7",
},]
}
];
const url = "role2";
const findAfterRefresh = (list, url) => {
for(const singleItem of Object.values(list)) {
if(singleItem.items && singleItem.items?.length > 0) {
return singleItem.items.find(child => child.role === url);
};
if(!singleItem.role.includes(url)) {
continue;
};
return singleItem;
}
};
findAfterRefresh(newList, url);

Dynamic array filtering by object property

I have a react live search dropdown component that filters through an array of objects by a search term. It filters my objects by title and then returns a list of all the related objects. This works fine.
Current:
Data Structure
data: [
{ id: 1, title: 'Some title here' },
{ id: 2, title: 'Another title' },
{ id: 3, title: 'last title' },
]
Component
<LiveSearch
term={term}
data={data} />
Inside Live search component
Filter data by term and render list
return data
.filter(item => item.title.toLowerCase().includes(term.toLowerCase())
.map((item, idx) => <li key={idx}>{item.title}</li>
My objects to search by are getting more advanced and what I would like to be able to do is pass into my component an array of property names I would like to compare to the search term.
My thinking process behind it is to loop through the object properties and if on of the properties matches the term the loop breaks and returns true adding that object to the list of items to be displayed.
Goal
Data Structure
data: [
{ id: 1, country: 'Canada', title: 'Some title here' },
{ id: 2, country: 'Australia', title: 'Another title' },
{ id: 3, country: 'Netherlands', title: 'last title' },
]
Component
<LiveSearch
searchFields={['country', 'title']}
term={term}
data={data} />
Inside Component filtering
return data
.filter(item => {
// Dynamic filtering of terms here
})
.map((item, idx) => <li key={idx}>{item.title}</li>
Inside the filter I'm trying to get a loop through the array and dynamically produce logic similar to this
item.searchFields[0].toLowerCase().includes(term.toLowerCase()) ||
item.searchFields[1].toLowerCase().includes(term.toLowerCase())
But obviously could loop over an infinite number of searchfields/properties
Use Array#some()
Something like
term = term.toLowerCase()
return data
.filter(item => {
return searchFields.some(field => item[field].toLowerCase().includes(term))
}).map(...
Check if some of the searchFields match:
// Checks wether a value matches a term
const matches = (value, term) => value.toLowerCase().includes(term.toLowerCase());
// Checks wether one of the fields in the item matcues the term
const itemMatches = (fields, term) => item => fields.some(field => matches(item[field], term);
// Filter the data to only contain items where on of the searchFields matches the term
const result = props.data.filter( itemMatches(props.searchFields, props.term) );
return result.map(item => <li key={idx}>{item.title}</li>);
You can use Array .some combined with .filter
let result = data.filter(obj =>
searchFields.some(s =>
obj[s] != undefined && obj[s].toLowerCase() === term
));
let data = [
{ id: 1, country: 'Canada', title: 'Some title here' },
{ id: 2, country: 'Australia', title: 'Another title' },
{ id: 3, country: 'Netherlands', title: 'last title' },
], searchFields = ["country", "title"], term = "canada";
let result = data.filter(obj =>
searchFields.some(s =>
obj[s] != undefined && obj[s].toLowerCase() === term
));
console.log(result);

Spread values after certain index

I am trying to spread array values into an object after a certain index 3 (column_4).
const array = ['Column_5', 'Column 6', 'Column 7']
const object = {
column_1: '',
column_2: 'Status',
column_3: 'Master',
column_4: 'Locale',
...array
}
At the moment, Column 5/6/7 appear at the start:
{
0: "Column_5",
1: "Column 6",
2: "Column 7",
column_1: "",
column_2: "Status",
column_3: "Master",
column_4: "Locale"
}
But I need them to appear in numerical order. Any ideas? I've tried using Ramda's insert method without any success.
const array = ['column_5', 'column_6', 'column_7']
const object = {
column_1: '',
column_2: 'Status',
column_3: 'Master',
column_4: 'Locale',
...array.reduce((acc, item) => {
acc[item] = item;
return acc;
}, {}),
}
console.log(object);

Categories

Resources