.filter and .map to occur only when length is greater than 0 - javascript

I'm trying to achieve the following:
For each object in addedExtra, if the field applicableOnProduct is empty, then I still want it to be rendered on the page however lets say an object that does have content in applicableOnProduct (length > 0) then it's only where I want the .filter and .map check to happen.
How do I do this in React?
const addedExtra = [{
name: 'Bed Sheet',
applicableOnProduct: [],
selected: false
},
{
name: 'Pillows',
applicableOnProduct: [],
selected: false
},
{
name: 'Luxury Bet Set',
applicableOnProduct: [{
title: 'Luxury Bed',
productId: '857493859049'
}],
selected: false
},
];
return (
{addedExtra
.filter(({ applicableOnProduct}) =>
applicableOnProduct.some(({ productId}) => productId === product.id)
)
.map((extra) => {
return <Extra {...extra}></Extra>;
})
}
)

Related

filter nested array of objects depending on children conditions

there is a tree of categories. its an array of objects (in this case 1 ob
ject with children property. children property is an array containing
or not containing other categories objects and so on ). Each object also has a property "disabled".
It can be either true or false. The starting point is that some bottommost children "disabled"
are set to true. In this case all of them are set to true. The task is to find parents ids
which disabled have to be set to true if all of its children disabled are set to true. In
this particular case all parents ids have to be found because all bottommost children disabled
are set to true. My function returns only lowest level of parents. What am I doing wrong?
let parentIdsToDisable = [];
function findParentIdsToDisable(tree) {
tree.forEach((category) => {
if (category.children.length > 0) {
if (category.children.every((child) => child.disabled === true)) {
category.disabled = true;
parentIdsToDisable.push(category.id);
}
}
findParentIdsToDisable(category.children);
});
}
const categories = [
{
id: 69,
name: 'Прикраси',
key: 'prykrasy',
description: 'Прикраси',
disabled: false,
mpath: '69.',
children: [
{
id: 70,
name: 'Аксесуари',
key: 'aksesyary-dlya-prykras',
description: 'Аксесуари для прикрас',
disabled: true,
mpath: '69.70.',
children: []
},
{
id: 72,
name: 'Ювелірні вироби',
key: 'uvelirni-vyroby',
description: 'Ювелірні вироби',
disabled: false,
mpath: '69.72.',
children: [
{
id: 73,
name: 'Срібло',
key: 'vyroby-iz-sribla',
description: 'Ювелірні вироби із срібла',
disabled: true,
mpath: '69.72.73.',
children: []
}
]
},
{
id: 71,
name: 'Біжутерія',
key: 'bizhuteriya',
description: 'Біжутерія',
disabled: true,
mpath: '69.71.',
children: []
}
]
}
]
You need to place the recursive call first, and also save the current item's id if it itself is disabled.
function findParentIdsToDisable(tree) {
tree.forEach((category) => {
findParentIdsToDisable(category.children);
if (category.disabled) {
parentIdsToDisable.push(category.id);
}
if (category.children.length > 0) {
if (category.children.every((child) => child.disabled === true)) {
category.disabled = true;
parentIdsToDisable.push(category.id);
}
}
});
}
This results in [ 70, 73, 72, 71, 69 ] for your data.
You need to recurse and apply the logic to each child before checking whether each child is disabled to see whether to disable the parent or not.
const categories=[{id:69,name:"Прикраси",key:"prykrasy",description:"Прикраси",disabled:!1,mpath:"69.",children:[{id:70,name:"Аксесуари",key:"aksesyary-dlya-prykras",description:"Аксесуари для прикрас",disabled:!0,mpath:"69.70.",children:[]},{id:72,name:"Ювелірні вироби",key:"uvelirni-vyroby",description:"Ювелірні вироби",disabled:!1,mpath:"69.72.",children:[{id:73,name:"Срібло",key:"vyroby-iz-sribla",description:"Ювелірні вироби із срібла",disabled:!0,mpath:"69.72.73.",children:[]}]},{id:71,name:"Біжутерія",key:"bizhuteriya",description:"Біжутерія",disabled:!0,mpath:"69.71.",children:[]}]}];
const disableObjectIfChildrenDisabled = (parent) => {
for (const child of parent.children ?? []) {
disableObjectIfChildrenDisabled(child);
}
if (parent.children?.every(child => child.disabled)) {
parent.disabled = true;
}
};
for (const parent of categories) {
disableObjectIfChildrenDisabled(parent);
}
console.log(categories);
Another approach would be to simply make a copy of the data with the changes already applied. This may or may not meet your needs, but it seems simpler than collecting the ids that need to change and doing something with them afterward.
This is a fairly simple recursive implementation of that idea:
const bubbleDisabled = (xs) =>
xs .map (({children, disabled, kids = bubbleDisabled (children), ...rest}) => ({
...rest,
disabled: disabled || (kids .length > 0 && kids .every (k => k .disabled)),
children: kids
}))
const categories = [{id: 69, name: "Прикраси", key: "prykrasy", description: "Прикраси", disabled: false, mpath: "69.", children: [{id: 70, name: "Аксесуари", key: "aksesyary-dlya-prykras", description: "Аксесуари для прикрас", disabled: true, mpath: "69.70.", children: []}, {id: 72, name: "Ювелірні вироби", key: "uvelirni-vyroby", description: "Ювелірні вироби", disabled: false, mpath: "69.72.", children: [{id: 73, name: "Срібло", key: "vyroby-iz-sribla", description: "Ювелірні вироби із срібла", disabled: true, mpath: "69.72.73.", children: []}]}, {id: 71, name: "Біжутерія", key: "bizhuteriya", description: "Біжутерія", disabled: true, mpath: "69.71.", children: []}]}]
console .log (bubbleDisabled (categories))
.as-console-wrapper {max-height: 100% !important; top: 0}
We need the length check because every will (rightly) return true for an empty array, and I assume we wouldn't want to bubble that up to its parents. Also we use disabled || because I assume if a node is already disabled, we wouldn't want to enable it based on the children. If either assumption is wrong, you can simplify the assignment of disabled.

Filter common elements of two arrays with useEffect?

I have these two states that consist in two arrays.
const bundle = [
{
id: 1,
type: "schedule",
action: "skip",
target_action: "reset"
},
{
id: 2,
type: "schedule",
action: "reset",
target_action: "skip"
},
{
id: 1,
type: "check",
action: "reset",
target_action: "skip"
},
{
id: 2,
type: "check",
action: "skip",
target_action: "reset"
}
];
const active = [
{
id: 1,
type: "schedule",
isActive: true
},
{
id: 2,
type: "schedule",
isActive: false
},
{
id: 1,
type: "check",
isActive: true
},
{
id: 2,
type: "check",
isActive: false
}
];
When items in active turns inactive (isActive: false) by clicking a button, they get filtered out of the array.
const handleActive = (item) => {
setActive((prevState) => {
const existingItem = prevState.find(
(activeItem) =>
activeItem.id === bundleItem.id &&
activeItem.type === bundleItem.type,
);
if (!existingItem) {
return [...active, { ...bundleItem, isActive: true }];
}
return prevState
.map((oldItem) => {
return oldItem.id === existingItem.id &&
oldItem.type === bundleItem.type
? { ...existingItem, isActive: !oldItem.isActive }
: oldItem;
})
.filter((itemToFilter) => itemToFilter.isActive);
});
};
Basically, I want to implement a useEffect that dynamically updates bundle in two ways simultaneously:
items must have at least one of action or c_action keys
when active gets updated (some elements get inactive and filtered out), I want to keep only the common items between the two arrays (same ID and type)
I implemented these two effects.
The first one to filter out the inactive elements from bundle:
React.useEffect(() => {
setBundle((prevState) => {
return bundle.filter((bundleItem) =>
active.some(
(activeItem) =>
activeItem.id === bundleItem.id &&
activeItem.type === bundleItem.type,
),
);
})
}, [active]);
The other one to filter out from bundle elements that doesn't "action" or "c_action" key.
React.useEffect(() => {
setBundle((prevState) => {
return bundle.filter(
(bundleItem) => bundleItem.action || bundleItem.c_action
);
});
}, [bundle]);
The second useEffect I implemented throws an infinite loop: bundle gets endlessly updated.
Thanks, a lot.
It seems to me that what you're looking for is actually an useMemo use case
you can do something like
const filteredBundle = useMemo(()=> bundle.filter(
(bundleItem) => bundleItem.action || bundleItem.c_action
),[bundle]);
And use the filtered bundle where makes sense

How to get value of their object in react hooks array?

Good afternoon, I rarely write here. But now I really can't understand.
I am using React Select to display select. In the onChange attribute, I pass a function that forms the object and writes it to UseStat. But then I try to find an object using the find and
take an array of values from it.
const [selectedSpecificationValues, setSelectedSpecificationValues] = useState([])
const setSelectedSpecificationValuesHandler = (e, s) => {
const maybeSelectedSpecification = selectedSpecificationValues.find(
ss => ss._id === s._id
)
const objForWrite = {
_id: s._id,
name: s.name,
values: e,
}
if (maybeSelectedSpecification) {
const index = selectedSpecificationValues.indexOf(
maybeSelectedSpecification
)
let newArr = [...selectedSpecificationValues]
newArr[index] = objForWrite
setSelectedSpecificationValues(newArr)
} else {
setSelectedSpecificationValues([
...selectedSpecificationValues,
objForWrite,
])
}
}
const ssTestVal = Id => {
let result = []
if (selectedSpecificationValues.length > 0) {
const foundItem = selectedSpecificationValues.find(i => i._id === Id)
console.log(Id, foundItem)
if (foundItem) {
result = foundItem.values
}
}
return result
}
/* specifications = [
{
values: [
{
value: 0,
label: '480 min',
},
{
value: 1,
label: '120 min',
},
],
_id: '5fe74eae07905e53ebf263ec',
name: 'Duration',
slug: 'duration',
createdAt: '2020-12-26T14:54:38.362Z',
updatedAt: '2020-12-29T08:37:18.962Z',
__v: 1,
},
{
values: [
{
value: 0,
label: 'Photobook',
},
{
value: 1,
label: 'Photocard',
},
{
value: 2,
label: 'Album',
},
{
value: 3,
label: 'DVD',
},
{
value: 4,
label: 'Stickers',
},
{
value: 5,
label: 'CD',
},
],
_id: '5fe74e9107905e53ebf263eb',
name: 'Includes',
slug: 'includes',
createdAt: '2020-12-26T14:54:09.267Z',
updatedAt: '2020-12-26T16:10:16.283Z',
__v: 9,
},
] */
{
specifications &&
specifications.map((s, idx) => (
<Select
classNamePrefix='select2-selection'
options={s.values}
value={() => ssTestVal(s._id)}
onChange={e => setSelectedSpecificationValuesHandler(e, s)}
isMulti
/>
))
}
It is also important to understand that I loop a lot of selections in order to select different characteristics and their values.
I will be glad to help!
https://codesandbox.io/s/serverless-night-kez18?file=/src/App.js
Looks like minor issue with how you were computing the value for the sub-select inputs. You were defining it as though it were a callback.
<Select
classNamePrefix="select2-selection"
options={s.values}
value={() => ssTestVal(s._id)} // <-- not correct, not a callabck
onChange={(e) => setSelectedSpecificationValuesHandler(e, s)}
isMulti
/>
It should just be immediately invoked to compute and return an input's value.
<Select
classNamePrefix="select2-selection"
options={s.values}
value={ssTestVal(s._id)} // <-- invoke immediately for return value
onChange={(e) => setSelectedSpecificationValuesHandler(e, s)}
isMulti
/>

JavaScript, looping, and functional approach

Data Structure coming back from the server
[
{
id: 1,
type: "Pickup",
items: [
{
id: 1,
description: "Item 1"
}
]
},
{
id: 2,
type: "Drop",
items: [
{
id: 0,
description: "Item 0"
}
]
},
{
id: 3,
type: "Drop",
items: [
{
id: 1,
description: "Item 1"
},
{
id: 2,
description: "Item 2"
}
]
},
{
id: 0,
type: "Pickup",
items: [
{
id: 0,
description: "Item 0"
},
{
id: 2,
description: "Item 2"
}
]
}
];
Each element represents an event.
Each event is only a pickup or drop.
Each event can have one or more items.
Initial State
On initial load, loop over the response coming from the server and add an extra property called isSelected to each event, each item, and set it as false as default. -- Done.
This isSelected property is for UI purpose only and tells user(s) which event(s) and/or item(s) has/have been selected.
// shove the response coming from the server here and add extra property called isSelected and set it to default value (false)
const initialState = {
events: []
}
moveEvent method:
const moveEvent = ({ events }, selectedEventId) => {
// de-dupe selected items
const selectedItemIds = {};
// grab and find the selected event by id
let foundSelectedEvent = events.find(event => event.id === selectedEventId);
// update the found event and all its items' isSelected property to true
foundSelectedEvent = {
...foundSelectedEvent,
isSelected: true,
items: foundSelectedEvent.items.map(item => {
item = { ...item, isSelected: true };
// Keep track of the selected items to update the other events.
selectedItemIds[item.id] = item.id;
return item;
})
};
events = events.map(event => {
// update events array to have the found selected event
if(event.id === foundSelectedEvent.id) {
return foundSelectedEvent;
}
// Loop over the rest of the non selected events
event.items = event.items.map(item => {
// if the same item exists in the selected event's items, then set item's isSelected to true.
const foundItem = selectedItemIds[item.id];
// foundItem is the id of an item, so 0 is valid
if(foundItem >= 0) {
return { ...item, isSelected: true };
}
return item;
});
const itemCount = event.items.length;
const selectedItemCount = event.items.filter(item => item.isSelected).length;
// If all items in the event are set to isSelected true, then mark the event to isSelected true as well.
if(itemCount === selectedItemCount) {
event = { ...event, isSelected: true };
}
return event;
});
return { events }
}
Personally, I don't like the way I've implemented the moveEvent method, and it seems like an imperative approach even though I'm using find, filter, and map.
All this moveEvent method is doing is flipping the isSelected flag.
Is there a better solution?
Is there a way to reduce the amount of looping? Maybe events should be an object and even its items. At least, the lookup would be fast for finding an event, and I don't have to use Array.find initially. However, I still have to either loop over each other non selected events' properties or convert them back and forth using Object.entries and/or Object.values.
Is there more a functional approach? Can recursion resolve this?
Usage and Result
// found the event with id 0
const newState = moveEvent(initialState, 0);
// Expected results
[
{
id: 1,
type: 'Pickup',
isSelected: false,
items: [ { id: 1, isSelected: false, description: 'Item 1' } ]
}
{
id: 2,
type: 'Drop',
// becasue all items' isSelected properties are set to true (even though it is just one), then set this event's isSelected to true
isSelected: true,
// set this to true because event id 0 has the same item (id 1)
items: [ { id: 0, isSelected: true, description: 'Item 0' } ]
}
{
id: 3,
type: 'Drop',
// since all items' isSelected properties are not set to true, then this should remain false.
isSelected: false,
items: [
{ id: 1, isSelected: false, description: 'Item 1' },
// set this to true because event id 0 has the same item (id 2)
{ id: 2, isSelected: true, description: 'Item 2' }
]
}
{
id: 0,
type: 'Pickup',
// set isSelected to true because the selected event id is 0
isSelected: true,
items: [
// since this belongs to the selected event id of 0, then set all items' isSelected to true
{ id: 0, isSelected: true, description: 'Item 0' },
{ id: 2, isSelected: true, description: 'Item 2' }
]
}
]
One of the problems with the current solution is data duplication. You are basically trying to keep the data between the different items in sync. Instead of changing all items with the same id, make sure there are no duplicate items by using an approach closer to what you would find in a rational database.
Let's first normalize the data:
const response = [...]; // data returned by the server
let data = { eventIds: [], events: {}, items: {} };
for (const {id, items, ...event} of response) {
data.eventIds.push(id);
data.events[id] = event;
event.items = [];
for (const {id, ...item} of items) {
event.items.push(id);
data.items[id] = item;
}
}
This should result in:
const data {
eventIds: [1, 2, 3, 0], // original order
events: {
0: { type: "Pickup", items: [0, 2] },
1: { type: "Pickup", items: [1] },
2: { type: "Drop", items: [0] },
3: { type: "Drop", items: [1, 2] },
},
items: {
0: { description: "Item 0" },
1: { description: "Item 1" },
2: { description: "Item 2" },
},
};
The next thing to realize is that the isSelected property of an event is computed based on the isSelected property of its items. Storing this would mean more data duplication. Instead calculate it though a function.
const response = [{id:1,type:"Pickup",items:[{id:1,description:"Item 1"}]},{id:2,type:"Drop",items:[{id:0,description:"Item 0"}]},{id:3,type:"Drop",items:[{id:1,description:"Item 1"},{id:2,description:"Item 2"}]},{id:0,type:"Pickup",items:[{id:0,description:"Item 0"},{id:2,description:"Item 2"}]}];
// normalize incoming data
let data = { eventIds: [], events: {}, items: {} };
for (const {id, items, ...event} of response) {
data.eventIds.push(id);
data.events[id] = event;
event.items = [];
for (const {id, ...item} of items) {
event.items.push(id);
data.items[id] = item;
item.isSelected = false;
}
}
// don't copy isSelected into the event, calculate it with a function
const isEventSelected = ({events, items}, eventId) => {
return events[eventId].items.every(id => items[id].isSelected);
};
// log initial data
console.log(data);
for (const id of data.eventIds) {
console.log(`event ${id} selected?`, isEventSelected(data, id));
}
// moveEvent implementation with the normalized structure
const moveEvent = (data, eventId) => {
let { events, items } = data;
for (const id of events[eventId].items) {
items = {...items, [id]: {...items[id], isSelected: true}};
}
return { ...data, items };
};
data = moveEvent(data, 0);
// log after data applying `moveEvent(data, 0)`
console.log(data);
for (const id of data.eventIds) {
console.log(`event ${id} selected? `, isEventSelected(data, id));
}
// optional: convert structure back (if you still need it)
const convert = (data) => {
const { eventIds, events, items } = data;
return eventIds.map(id => ({
id,
...events[id],
isSelected: isEventSelected(data, id),
items: events[id].items.map(id => ({id, ...items[id]}))
}));
};
console.log(convert(data));
Check browser console, for better ouput readability.
I'm not sure if this answers solves your entire problem, but I hope you got something useful info out of it.

Recursive Array Modification

I would like to copy an array so as not to modify the original, and remove all selected: false from new array, and return this new array. The array is infinitely nested, and with infinite property names non of which are predictable, so this should be possible through iteration looking at the value of each property for Array.isArray(). While I can remove selected:false objects in the iteration, I fail to return the modified array back to the new array.
function failing to filter aliens. Also, function works in CodePen, but not in my code.
// sample nested data
var data = [
{
partyId: "animal-ID-001",
selected: false,
members: [
{
selected: false,
userId: "animal-user-3443"
},
{
selected: false,
userId: "animal-user-3444"
}
]
},
{
partyId: "benjamin-ID-002",
selected: true,
members: [
{
selected: true,
userId: "benjamin-user-5567",
teams: [
{
selected: true,
teamId: "team-benjamin-678"
},
{
selected: false,
teamId: "team-benjamin-3468"
}
]},
{
selected: false,
userId: "benjamin-user-55671"
}
]
},
{
partyId: "crystal-ID-003",
selected: true,
members: [
{
selected: true,
userId: "crystal-user-8567"
},
{
selected: true,
userId: "crystal-user-85671"
}
],
aliens: [
{
selected: false,
alienId: "crystal-alien-467"
},
{
selected: false,
alienId: "crystal-alien-230"
}
]
}
];
// remove selected false from array
// updated per suggestions
function updateState(arr) {
return arr.filter(obj => obj.selected ).map( obj => {
for (var prop in obj) {
if( Array.isArray( obj[prop] ) ) {
return { ...obj, [prop]: updateState( obj[prop] ) };
}
}
return { ...obj }
});
}
console.log( updateState( data ) );
Does something like this work for you?:
const removeNonselected = (x) =>
Array .isArray (x)
? x .reduce (
(all, item) => item .selected === false
? all
: all .concat (removeNonselected (item)),
[]
)
: typeof x == 'object'
? Object .fromEntries (
Object .entries (x) .map(([n, v]) => [n, removeNonselected(v)])
)
: x
const data = [{partyId: "animal-ID-001", selected: false, members: [{selected: false, userId: "animal-user-3443"}, {selected: false, userId: "animal-user-3444"}]}, {partyId: "benjamin-ID-002", selected: true, members: [{selected: true, userId: "benjamin-user-5567", teams: [{selected: true, teamId: "team-benjamin-678"}, {selected: false, teamId: "team-benjamin-3468"}]}, {selected: false, userId: "benjamin-user-55671"}]}, {partyId: "crystal-ID-003", selected: true, members: [{selected: true, userId: "crystal-user-8567"}, {selected: true, userId: "crystal-user-85671"}], aliens: [{selected: false, alienId: "crystal-alien-467"}, {selected: false, alienId: "crystal-alien-230"}]}];
console .log (removeNonselected (data))
console .log ('original data unchanged:')
console .log (data)
This handles three cases: where the data is an array, where it's an object, or where it's something else. For an array we keep only the selected ones (where selected is not false) and recurs on those values. For an object, we keep other values intact, but recur on array properties. Anything else we just return as is.
This does not remove a selected: false property of an object, only from within an array. It would not be much harder to add that, but it didn't seem to be in your requirements.
If you environment doesn't support Object.fromEntries, it's fairly easy to shim.
First .filter the original data, removing items with selected: false, then .map the result, and inside the callback, return the same object while .filtering the members property. Then
var data = [{
partyId: "animal-ID-001",
selected: false,
members: [{
selected: false,
userId: "animal-user-3443"
},
{
selected: false,
userId: "animal-user-3444"
}
]
},
{
partyId: "benjamin-ID-002",
selected: true,
members: [{
selected: true,
userId: "benjamin-user-5567"
},
{
selected: false,
userId: "benjamin-user-55671"
}
]
},
{
partyId: "crystal-ID-003",
selected: true,
members: [{
selected: true,
userId: "crystal-user-8567"
},
{
selected: true,
userId: "crystal-user-85671"
}
]
}
];
const updatedData = data
.filter(({ selected }) => selected)
.map(({ members, ...rest }) => ({
...rest,
members: members.filter(({ selected }) => selected)
}));
console.log(updatedData);
Try this simple one:
let selectedParties = data.filter(item => item.selected);
let partiesWithSelectedMembersOnly = selectedParties.map(item => {
return {
...item,
members: item.members.filter(member => member.selected)
};
});
Array.filter() returns new array, so you will not modify initial one.
Don't reinvent the wheel, check what functions you can use on arrays. Array.filter with recursion is perfect here:
var data=[{partyId:"animal-ID-001",selected:!1,members:[{selected:!1,userId:"animal-user-3443"},{selected:!1,userId:"animal-user-3444"}]},{partyId:"benjamin-ID-002",selected:!0,members:[{selected:!0,userId:"benjamin-user-5567"},{selected:!1,userId:"benjamin-user-55671"}]},{partyId:"crystal-ID-003",selected:!0,members:[{selected:!0,userId:"crystal-user-8567"},{selected:!0,userId:"crystal-user-85671"}]}];
function removeNonselected(arr) {
return arr.filter(obj => obj.selected).map(obj => {
if(obj.members) return { ...obj, members: removeNonselected(obj.members) };
else return { ...obj }
});
}
console.log(removeNonselected(data));

Categories

Resources