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);
CodePudding user response:
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.
CodePudding user response:
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);
CodePudding user response:
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));