How to remove an element from a key value array? - javascript

I want to remove a specific element from an array, I am getting the key of the element from the input.
I want to be able to remove the element only by knowing the key.
This is the array:
state ={
splitAmount : [{
"SplitAmount0": this.props.data.amount1
}, {
"SplitAmount1": this.props.data.amount2
}, {
"SplitAmount2": this.props.data.amount3
}]
}
Remove function:
removeSplitAmount(e) {
console.log("remove",e.target.name)
let array = [...this.state.splitAmount];
let index = this.state.splitAmount.IndexOf(p => p == e.target.name )
if (index !== -1) {
array.splice(index, 1);
this.setState({splitAmount: array});
}
}

You can use the .filter method on the array combined with the Object.keys to clean the function up a lot:
removeSplitAmount(e) {
const newSplitAmount = this.state.splitAmount
.filter(p => !Object.keys(p).includes(e.target.name));
this.setState({ splitAmount: newSplitAmount });
}

You can use hasOwnProperty to filter objects you need.
removeSplitAmount(e) {
const newSplitAmount = this.state.splitAmount
.filter(x => !x.hasOwnProperty(e.target.name));
this.setState({ splitAmount: newSplitAmount });
}

As Dmitry said you can't do a indexOf on an array of objects... i felt bad i didn't realize that.
Would be useful on this case:
var beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];
beasts.indexOf('bison')
On your case you are better to go with the .filter method as said in above answers because you are searching for and object with a specific property

Related

Using map reduce etc, how would you find the first item matching a certain criteria in a nested array, and stop once found?

How would you find the first item matching a certain criteria in a nested array, and stop once found?
In a 1D array, this is what the Array.find function is for, but how would you do it for a 2D array, and, even neater, for n-dimension array?
Also, I'm trying to come up with a neat solution using es6 and array functions such as find, map, reduce etc, rather than using more traditional loops and variables to maintain state (see one such old-school solution below).
The data may look something like this
const data = [
{arr: [{val:6,name:'aaa'},{val:4,name:'bbb'},{val:8,name:'ccc'}]},
{arr: [{val:3,name:'mmm'},{val:5,name:'nnn'},{val:9,name:'ppp'},{val:5,name:'ooo'}]}
]
I'm hoping I can do something similar to array.find (and its predicate / testing function), but I need to go deeper and find eg the first item with val=5. For the data above, I'd expect to get the item with name 'nnn' (not 'ooo'), and have the process end once the first item is found. Similar to Array.find, I want to avoid processing the rest of the data once a matching item is found.
One boring old way to do it would be something like this, with a loop, but that's... boring, and not as neat as the lovely array functions :)
let found
// loop through all data entries in the outer array
for (const d of data) {
// attempt to find a matching item in the inner array.
// using array.find means we stop at the first match, yay!
const theItem = d.arr.find(item => {
return myPredicate(item)
})
// we also need to break out of the loop. ugh!
if (theItem) {
found = theItem
break
}
}
// return what we found (may be undefined)
return found
Now, I realise that I can do something with find() and some(), say, similar to the answer here ES6 - Finding data in nested arrays, but the problem is that using find on the outer array means that we get back the first item of the outer data array, whereas I want an item from the inner arr array.
const outer = data.find(d => {
return d.arr.some(item => {
return myPredicate(item)
})
})
I would then have to process outer AGAIN to find the item in outer.arr, something like
outer.arr.find(item => myPredicate(item))
This doesn't sit well with me, as the call to some(...) has already gone through and found the matching inner item!
I thought this would be straight forward, and maybe it is, but for one reason or another I got stuck on this little challenge.
I've also looked at the nice traverse library (https://www.npmjs.com/package/traverse), but again that seems to be more about traversing through a whole tree rather than stopping and returning once a particular node is found.
Anyone up for a challenge? ;)
The easiest (though slightly ugly) solution would be to assign the matching item to an outer variable when found:
let foundNested;
data.some(subarr => (
subarr.some((item) => {
if (myPredicate(item)) {
foundNested = item;
return true;
}
});
});
You might use .reduce to avoid assigning to an outer variable:
const myPredicate = ({ val }) => val === 5;
const data = [
{arr: [{val:6,name:'aaa'},{val:4,name:'bbb'},{val:8,name:'ccc'}]},
{arr: [{val:3,name:'mmm'},{val:5,name:'nnn'},{val:9,name:'ppp'},{val:5,name:'ooo'}]}
];
const found = data.reduce((a, { arr }) => (
a ||
arr.find(myPredicate)
), null);
console.log(found);
Problem is, the reduce won't short-circuit - it'll fully iterate over the outer array regardless. For true short-circuiting, I think I'd prefer using a for..of loop:
const data = [
{arr: [{val:6,name:'aaa'},{val:4,name:'bbb'},{val:8,name:'ccc'}]},
{arr: [{val:3,name:'mmm'},{val:5,name:'nnn'},{val:9,name:'ppp'},{val:5,name:'ooo'}]}
];
function findNested(outerArr, myPredicate) {
for (const { arr } of outerArr) {
for (const item of arr) {
if (myPredicate(item)) {
return item;
}
}
}
}
const myPredicate = ({ val }) => val === 5;
console.log(findNested(data, myPredicate));
You'll want to write your own find function that doesn't take a predicate but a result-producing callback:
function find(iterable, callback) {
for (const value of iterable) {
const result = callback(value);
if (result !== undefined)
return result;
}
}
With that, you can write
const data = [
{arr: [{val:6,name:'aaa'},{val:4,name:'bbb'},{val:8,name:'ccc'}]},
{arr: [{val:3,name:'mmm'},{val:5,name:'nnn'},{val:9,name:'ppp'},{val:5,name:'ooo'}]}
];
console.log(find(data, ({arr}) => find(arr, o => o.val == 5 ? o : undefined)));
Alternatively, if you want to get all results, flatMap is the perfect tool:
data.flatMap(({arr}) => arr.filter(({val}) => val == 5));
Sure, why not. I'm up for it. This can probably be improved upon. But this will work. Let's say you are trying to find an object with id of 5 in a multidimensional array.
const arr = [[[{id: 1}], [{id: 2}]], [[{id: 3}]], [[{id: 4}], [{id: 5}], [{id: 6}]]]
function findObject (obj) {
if (Array.isArray(obj)) {
const len = obj.length
for (let i = 0; i < len; i++) {
const found = findObject(obj[i])
if (found) {
return found
}
}
} else if (obj.id === 5) { // Put your search condition here.
return obj
}
}
const obj = findObject(arr)
console.log('obj: ', obj)
This seems to work, but, in my opinion, it's still not clean with that 'found' variable sitting outside the main block and being assigned from inside the nested find block. It's better though. Thoughts?
let found
data.find(d =>
d.arr.find(item => {
found = myPredicate(item) ? item : void 0
return found !== void 0
}) !== void 0
)
return found

How to setState to update an array in React?

I need to update an array in the state of my component in React.
I've seens several topic with this question, but so far all of them are adding new items to the array with the spread operator, but I need to add OR remove items on a callback like this:
handleCheck (newStatus, isChecked) {
this.setState({ filterStatus: [...this.state.filterStatus, newStatus] })
}
But the problem here is that it didn't work for status where the isChecked boolean comes false to remove them from the array
What is the best way to add or remove items from that array, hopefully with spread operator?
Thanks
try to use the .filter to remove the element. Remember to duplicate the array (using [...array] syntax) before using .filter, to don't change the original array:
handleCheck (newStatus, isChecked) {
let newArray = isChecked? // if isChecked is true
[...this.state.filterStatus, newStatus] : // add element
[...this.state.filterStatus].filter(e => e !== newStatus); // remove the elements that are equal to newStatus
this.setState({ filterStatus: newArray})
}
Think it functionnal !
const prevArray = this.state.array;
const itemsToAdd = [item, item, item];
//this function map the prev array escaping elements to remove
//and then push itemsToAdd to the new array
const updateArray = ( i = [0], newArray = [] ) =>
i < prevArray ?
yourRemovingCondition(prevArray[i]) ?
updateArray( i + 1, newArray )
: updateArray( i + 1, [...newArray, prevArray[i])
: [...newArray, ...itemsToAdd]
;
setState({array: updateArray()];
Put the check, add the element only when the bool isChecked is true, otherwise use filter to remove the element from the array.
Like this:
handleCheck (newStatus, isChecked) {
this.setState(prevState => {
let filterStatus;
if (isChecked) {
filterStatus = [...prevState.filterStatus, newStatus];
} else {
filterStatus = prevState.filterStatus.filter(el => el.id !== newStatus.id)
}
return { filterStatus }
})
}
Note: In place of id in filter callback method use the correct unique property name.

Doing map and filter an array in reactjs

I have a list of object:
a = [{number:1},{category:[abc, cde]},{class:2}],
[{number:2},{category:[abc, def]},{class:3}],
[{number:3},{category:[def]},{class:4}]
below is my code:
b
.filter((a, index) => (a.category === 'def'))
.map((a, index) => (
<div>{a.number}</div>
)
)
I need to list down object from 'a' which contain category = 'def'. Seems like I'm unable to filter it because my 'category' is in array format.
How do you guys fix it?
b
.filter((v,i) => (v.category.includes('def')))
.map((v,i) => (
<div>{v.number}</div>
))
Simply checks if def is in the array like:
a.category.includes('def')
Your objects are stored as an array of objects, which means you can't "key in" to check. So you have to check all of the elements of the array to see if 1. the key category exists and 2. see if def is in the object that contains category.
Array#includes has an older brother that is often overlooked. Array#some, which accepts a callback that will do what you need.
Then you have to use Array#find to traverse the filtered arrays again to find the object that has the key number and then print that value. You can also do this manually by using the index number of those objects, but it's far less robust.
a = [[{number:1},{category:['abc', 'cde']},{class:2}],
[{number:2},{category:['abc', 'def']},{class:3}],
[{number:3},{category:['def']},{class:4}]]
h = a.filter((arr) => {
return arr.some((obj) => {
if (obj.category) {
return obj.category.includes('def')
} else {
return false
}
})
})
console.log(h)
j = h.map((arr) => {
let numObject = arr.find((obj) => ('number' in obj));
if (numObject) {
return numObject.number
} else {
return //something
}
})
console.log(j)

JavaScript or Lodash find objects by key

In an array of objects with diff keys, how do I find objects by key using ES6 or Lodash?
const arr = [{a:2}, {b:3}, {fred:10}]
I want the result to be:
=> [{a:2}, {fred:10}]
I don't want to use an omit style approach.
const filtered = arr.filter(obj => obj.hasOwnProperty("a") || obj.hasOwnProperty("fred"));
// or, if you have dynamic / lots of keys:
const keys = ["a", "fred"];
const filtered = arr.filter(obj => keys.some(key => obj.hasOwnProperty(key));
filter method will be useful. Create a function and pass an array of keys. Inside filter function check if the key is matching with the parameter array. If it passed then return that object
var orgObject = [{
a: 2
}, {
b: 3
}, {
fred: 10
}];
function searchByKey(keyNames) {
return orgObject.filter(function(item) {
for (var keys in item) {
if (keyNames.indexOf(keys) !== -1) {
return item
}
}
})
}
console.log(searchByKey(['a', 'fred']))
Basically you want all the objects from the array who have the fields a or fred. You can use the hasOwnProperty() on the objects while filtering.
_.filter(array, elem => elem.hasOwnProperty('a') || elem.hasOwnProperty('fred'));

How to remove an object from an array in Immutable?

Given a state like this:
state = {
things: [
{ id: 'a1', name: 'thing 1' },
{ id: 'a2', name: 'thing 2' },
],
};
How can I create a new state where ID "a1" is removed? It's easy enough to push new items:
return state.set(state.get('things').push(newThing));
But I can't figure out how to search for and remove an object by its id property. I tried this:
return state.set('tracks',
state.get('tracks').delete(
state.get('tracks').findIndex(x => x.get('id') === 'a2')
)
)
But it seems messy, plus it only works if the item is found, because if findIndex returns -1, that's a valid value for delete.
You can use Array#filter.
return state.set('things', state.get('things').filter(o => o.get('id') !== 'a1'));
When you are using filter it iterates all cycle -> one effective way is finding index => slice and using splitter ...
const index = state.findIndex(data => data.id === action.id);
return [...state.slice(0, index), ...state.slice(index + 1)];
Alternatively, as you are "searching and then deleting"...
var itemIndex = this.state.get("tracks").findIndex(x => x.get('id') === 'a2');
return itemIndex > -1 ? this.state.deleteIn(["tracks", itemIndex]) : this.state;
This will ensure the state is not mutated when there are no changes.
Found this thread while looking for a solution to a similar task.
Solved it with update method:
return state.update('things', (things) => things.filter((t) => t.id !== action.things.id))
any idea/comment which one is better/preferred?
You can do that even without immutable.js with following function.
function arrayFilter(array, filter) {
let ret = array
let removed = 0
for (let index = 0; index < array.length; index++) {
const passed = filter(array[index], index, array)
if (!passed) {
ret = [...ret.slice(0, index - removed), ...ret.slice(index - removed + 1)]
removed++
}
}
return ret
}
ImmutableJS working with nested arrays
Immutablejs is great but at the same time makes things more complicated in some edge cases, particularly when working with nested arrays.
Sometimes it is easier to take it back to JS in a general sense for this particular issue.
// 1. get a copy of the list into normal JavaScript
const myList = state.getIn(['root', 'someMap', 'myList']).toJS()
// 2. remove item in list using normal JavaScript and/or anything else
myList.splice(deleteIndex, 1)
// 3. return the new state based on mutated myList
return state
.mergeDeep({ root: { someMap: { myList: undefined } }})
.mergeDeep({ root: { someMap: { myList } }})
Unfortunately, step 3 is necessary to specifically set to undefined because if you simply set myList directly as an array value, ImmutableJS will do a comparison of values between the current list and only modify them creating strange behavior.
The justification for this is to simplify the mental overhead. I do not recommend doing this in a loop, rather manipulate the pure JS array in a loop if you must but should be prior to step 3.

Categories

Resources