Delete an array item from an array in reactjs [duplicate] - reactjs

I am trying to remove a (semi) deeply nested item from an array using setState but it doesn't seem to be working. My state is structured as follows:
state = {
currentSeries: null,
currentRowIndex: null,
rows: [
{
id: shortid.generate(),
nodes: [],
series: [], // array with item I want to remove
},
],
};
and my remove item call:
onRemoveModelElementClick = (rowId, modelElementId) => {
this.setState((prevState) => {
const index = prevState.rows.findIndex(x => x.id === rowId);
const series = prevState.rows[index].series.filter(s => s.id !== modelElementId);
return series;
});
};
I tried spreading the remaining state is several ways but it does not seem to update properly. I the rowId and modelElementId are correct and I can verify they do filter the correct item out. I am just having trouble on what to return. I know it is something simple but for the life of me I can't see it.

My recommendation would be to use .map to make things are bit easier to digest. You can then write it like so:
onRemoveModelElementClick = (rowId, modelElementId) => {
const updatedRowsState = this.state.rows.map(row => {
// this is not the row you're looking for so return the original row
if (row.id !== rowId) {
return row;
}
const filteredSeries = row.series.filter(s => s.id !== modelElementId);
return {
// spread properties (id, node, series)
...row,
// overwrite series with item filtered out
series: filteredSeries,
};
});
// since rest of the state doesn't change, we only need to update rows property
this.setState('rows', updatedRowsState);
}
Hope this helps and let me know if you have any questions.

I think the issue here is how your code uses setState. The setState function must return an object. Assuming your filtering functions are correct as you describe, return an object to update the state:
return { series };
setState documentation

Here is what I did to get it working in case it can help someone else:
onRemoveModelElementClick = (rowId, modelElementId) => {
this.setState((prevState) => {
const updatedRowState = prevState.rows.map((row) => {
if (row.id !== rowId) {
return row;
}
const filteredSeries = row.series.filter(s => s.id !== modelElementId);
return {
...row,
series: filteredSeries,
};
});
return {
rows: updatedRowState,
};
});
};
All credit to Dom for the great idea and logic!

Related

after sort, the ui not render with new data

im trying to list out the list after sort by status. This is my code, it can be sort in order.But the problem is, at the ui screen, it still previous value which it not render after do sorting.
const [listData, setListData] = useState(null)
let statusOrder={
'Active':1,
'In progress'
}
let list=[
{
sample: '1',
data:[
{
name:'name1',
status:'In Progress'
},
{
name:'name2',
status:'Active'
}
]
},
{
sample:'2',
data:[
{
name:'name1',
status:'In Progress'
},
{
name:'name2',
status:'Active'
}
]
}
]
const function = () => {
setListData(list)
for(let i=0; i<listData.length; i++){
listData[i].data.sort(a,b) => statusOrder[a.status] - statusOrder[b.status]
if((index+1) == listData.length)
setData(listData)
}
}
useEffect(() => {
sort()
},[])
I already try this approach, but still not render.
setListData([].concat(listData[i].data).sort((a, b) => statusOrder[a.status] - statusOrder[b.status]))
Like mentioned in the comments you're not really setting a new state since the "new" state is the same object as the old one.
Your function is called function which is a reserved keyword in the JS. So I assume you mean sort like you're using in your useEffect.
To handle the sort or your list you can try this
const sort = () => {
// give a callback to the setState which receives the prevState
setListData((prevListData) => {
// map over the prevState which returns a new array
const newListData = prevListData.map((item) => {
// return a new object with the item and sorted data
return {
...item,
data: item.data.sort(
(a, b) => statusOrder[a.status] - statusOrder[b.status]
),
};
});
return newListData;
});
};

updating object inside array inside object using prevState and the useState hook

I'd like to remove a nested object based on the id is equal to a passed prop. At the moment, the entire object is replaced. I'm missing something, when trying to update the state using useState probably with the way I'm looping my object?
UPDATE: The question was closed in response to available answers for updating nested objects. This question involves arrays which I believe are part of the issue at hand. Please note the difference in nature in this question with the forEach. Perhaps a return statement is required, or a different approach to the filtering on id..
my initial object looks like this:
{
"some_id1": [
{
"id": 93979,
// MORE STUFF
},
{
"id": 93978,
// MORE STUFF
}
],
"some_id2": [
{
"id": 93961,
// MORE STUFF
},
{
"id": 93960,
// MORE STUFF
}
]
}
and I go through each item as such:
for (const key in items) {
if (Object.hasOwnProperty.call(items, key)) {
const element = items[key];
element.forEach(x => {
if (x.id === singleItem.id) {
setItems(prevState => ({
...prevState,
[key]: {
...prevState[key],
[x.id]: undefined
}
}))
}
})
}
There are 3 problems in your code:
You are setting the value of key to an object while the items is expected to have an array to ids.
// current
[key]: {
...prevState[key],
[x.id]: undefined
}
// expected
[key]: prevState[key].filter(item => item.id === matchingId)
If you intend to remove an object from an array based on some condition, you should be using filter as pointed out in Owen's answer because what you are doing is something else:
const a = { xyz: 123, xyz: undefined };
console.log(a); // { xyz: undefined} - it did not remove the key
To make your code more readable, it is expected to manipulate the entire object items and then, set it to the state once using setItems - in contrast to calling setItems multiple times inside a loop and based on some condition.
This makes your code more predictable and leads to fewer re-renders.
Also, the solution to your problem:
// Define this somewhere
const INITIAL_STATE = {
"some_id1": [
{
"id": 93979
},
{
"id": 93978
}
],
"some_id2": [
{
"id": 93961
},
{
"id": 93960
}
]
};
// State initialization
const [items, setItems] = React.useState(INITIAL_STATE);
// Handler to remove the nested object with matching `id`
const removeByNestedId = (id, items) => {
const keys = Object.keys(items);
const updatedItems = keys.reduce((result, key) => {
const values = items[key];
// Since, we want to remove the object with matching id, we filter out the ones for which the id did not match. This way, the new values will not include the object with id as `id` argument passed.
result[key] = values.filter(nestedItem => nestedItem.id !== id)
return result;
}, {});
setItems(updatedItems);
}
// Usage
removeByNestedId(93961, items);
Probably a simple reduce function would work, Loop over the entries and return back an object
const data = {"some_id1": [{"id": 93979},{"id": 93978}],"some_id2": [{"id": 93961},{"id": 93960}]}
const remove = ({id, data}) => {
return Object.entries(data).reduce((prev, [nextKey, nextValue]) => {
return {...prev, [nextKey]: nextValue.filter(({id: itemId}) => id !== itemId)}
}, {})
}
console.log(remove({id: 93961, data}))
your way solution
for (const key in items) {
if (Object.hasOwnProperty.call(items, key)) {
const element = items[key];
element.forEach(x => {
if (x.id === singleItem.id) {
setItems(prevState => ({
...prevState,
//filter will remove the x item
[key]: element.filter(i => i.id !== x.id),
}))
}
})
}
}
short solution.
for(const k in items) items[k] = items[k].filter(x => x.id !== singleItemId);
const items = {
"some_id1": [
{
"id": 93979,
},
{
"id": 93978,
}
],
"some_id2": [
{
"id": 93961,
},
{
"id": 93960,
}
]
}
const singleItemId = 93979;
for (const k in items) items[k] = items[k].filter(x => x.id !== singleItemId);
console.log(items);
//setItems(items)
You could try using the functional update.
const [data, setData] = [{id:1},{id:2},{id:3}...]
Once you know the id which you need to remove.
setData(d=>d.filter(item=>item.id !== id));

How to create multiple object in Reactjs?

I am having a onChange function i was trying to update the array optionUpdates which is inside of sentdata by index wise as i had passed the index to the onChange function.
Suppose i update any two values of the input field from option which is inside of postdata therefore the input name i.e. orderStatus with changed value and with order should be saved inside of optionUpdates
For example: Suppose i update the option 1 and option 3 of my postdata further inside of options of orderStatus values so my optionUpdates which is inside of sentdata should look like this
optionUpdates: [
{
Order: 1,
orderStatus: "NEW1"
},
{
Order: 3,
orderStatus: "New2"
}
]
here is what i tried
setSentData(oldValue => {
const curoptions = oldValue.sentdata.optionUpdates[idx];
console.log(curoptions);
curoptions.event.target.name = event.target.value;
return {
...oldValue,
sentdata: {
...oldValue.sentdata.optionUpdates,
curoptions
}
};
});
};
Demo
complete code:
import React from "react";
import "./styles.css";
export default function App() {
const x = {
LEVEL: {
Type: "LINEN",
options: [
{
Order: 1,
orderStatus: "INFO",
orderValue: "5"
},
{
Order: 2,
orderStatus: "INPROGRESS",
orderValue: "5"
},
{
Order: 3,
orderStatus: "ACTIVE",
orderValue: "9"
}
],
details: "2020 N/w UA",
OrderType: "Axes"
},
State: "Inprogress"
};
const [postdata, setPostData] = React.useState(x);
const posting = {
optionUpdates: []
};
const [sentdata, setSentData] = React.useState(posting);
const handleOptionInputChange = (event, idx) => {
const target = event.target;
setPostData(prev => ({
...prev,
LEVEL: {
...prev.LEVEL,
options: prev.LEVEL.options.map((item, id) => {
if (id === idx) {
return { ...item, [target.name]: target.value };
}
return item;
})
}
}));
setSentData(oldValue => {
const curoptions = oldValue.sentdata.optionUpdates[idx];
console.log(curoptions);
curoptions.event.target.name = event.target.value;
return {
...oldValue,
sentdata: {
...oldValue.sentdata.optionUpdates,
curoptions
}
};
});
};
return (
<div className="App">
{postdata.LEVEL.options.map((item, idx) => {
return (
<input
key={idx}
type="text"
name="orderStatus"
value={postdata.LEVEL.options[idx].orderStatus}
onChange={e => handleOptionInputChange(e, idx)}
/>
);
})}
</div>
);
}
If I've understood correctly then what you're looking to do is save a copy of the relevant options object in sentdata every time one changes. I think the best way to approach this is by doing all your state modification outside of setPostData, which then makes the results immediately available to both setPostData and setSentData. It will also make the setters easier to read, which is good because you have some quite deeply nested and complicated state here.
A few other things worth noting first:
Trying to use synchronous event results directly inside the asynchronous setter functions will throw warnings. If you do need to use them inside setters, then it is best to destructure them from the event object first. This implementation uses destructuring although it didn't end up being necessary in the end.
You seem to have got a bit muddled up with setSentData. The oldValue parameter returns the whole state, as prev in setPostData does. For oldValue.sentdata you just wanted oldValue. You also wanted curoptions[event.target.name], not curoptions.event.target.name.
So, on to your code. I would suggest that you change the way that your input is rendered so that you are using a stable value rather than just the index. This makes it possible to reference the object no matter which array it is in. I have rewritten it using the Order property - if this value is not stable then you should assign it one. Ideally you would use a long uuid.
{postdata.LEVEL.options.map(item => {
return (
<input
key={item.Order}
type="text"
name="orderStatus"
value={item.orderStatus}
onChange={e => handleOptionInputChange(e, item.Order)}
/>
);
})}
The handleOptionInputChange function will now use this Order property to find the correct objects in both postdata and sentdata and update them, or if it does not exist in sentdata then push it there. You would do this by cloning, modifying, and returning the relevant array each time, as I explained before. Here is the function again with comments:
const handleOptionInputChange = (event, orderNum) => {
const { name, value } = event.target;
/* Clone the options array and all objects
inside so we can mutate them without
modifying the state object */
const optionsClone = postdata.LEVEL.options
.slice()
.map(obj => Object.assign({}, obj));
/* Find index of the changed object */
const optionIdx = optionsClone.findIndex(obj => obj.Order === orderNum);
/* If the orderNum points to an existing object...*/
if (optionIdx >= 0) {
/* Change the value of object in clone */
optionsClone[optionIdx][name] = value;
/* Set postdata with the modified optionsClone */
setPostData(prev => ({
...prev,
LEVEL: {
...prev.LEVEL,
options: optionsClone
}
}));
/* Clone the optionUpates array and all
contained objects from sentdata */
const updatesClone = sentdata.optionUpdates
.slice()
.map(obj => Object.assign({}, obj));
/* Find the index of the changed object */
const updateIdx = updatesClone.findIndex(obj => obj.Order === orderNum);
/* If the changed object has already been
changed before, alter it again, otherwise push
a new object onto the stack*/
if (updateIdx >= 0) {
updatesClone[updateIdx][name] = value;
} else {
updatesClone.push({ Order: orderNum, [name]: value });
}
/* Set sentdata with modified updatesClone */
setSentData(prev => ({
...prev,
optionUpdates: updatesClone
}));
}
};

React - remove nested array item using setState function call

I am trying to remove a (semi) deeply nested item from an array using setState but it doesn't seem to be working. My state is structured as follows:
state = {
currentSeries: null,
currentRowIndex: null,
rows: [
{
id: shortid.generate(),
nodes: [],
series: [], // array with item I want to remove
},
],
};
and my remove item call:
onRemoveModelElementClick = (rowId, modelElementId) => {
this.setState((prevState) => {
const index = prevState.rows.findIndex(x => x.id === rowId);
const series = prevState.rows[index].series.filter(s => s.id !== modelElementId);
return series;
});
};
I tried spreading the remaining state is several ways but it does not seem to update properly. I the rowId and modelElementId are correct and I can verify they do filter the correct item out. I am just having trouble on what to return. I know it is something simple but for the life of me I can't see it.
My recommendation would be to use .map to make things are bit easier to digest. You can then write it like so:
onRemoveModelElementClick = (rowId, modelElementId) => {
const updatedRowsState = this.state.rows.map(row => {
// this is not the row you're looking for so return the original row
if (row.id !== rowId) {
return row;
}
const filteredSeries = row.series.filter(s => s.id !== modelElementId);
return {
// spread properties (id, node, series)
...row,
// overwrite series with item filtered out
series: filteredSeries,
};
});
// since rest of the state doesn't change, we only need to update rows property
this.setState('rows', updatedRowsState);
}
Hope this helps and let me know if you have any questions.
I think the issue here is how your code uses setState. The setState function must return an object. Assuming your filtering functions are correct as you describe, return an object to update the state:
return { series };
setState documentation
Here is what I did to get it working in case it can help someone else:
onRemoveModelElementClick = (rowId, modelElementId) => {
this.setState((prevState) => {
const updatedRowState = prevState.rows.map((row) => {
if (row.id !== rowId) {
return row;
}
const filteredSeries = row.series.filter(s => s.id !== modelElementId);
return {
...row,
series: filteredSeries,
};
});
return {
rows: updatedRowState,
};
});
};
All credit to Dom for the great idea and logic!

change of one value in the state react

In the state I have data array that look like this
{
"offers" : [
{
"model": "shoes",
"availability": false,
"id": 1
},
{
"model": "t-shirt",
"availability": true,
"id": 2
},
{
"make": "belt",
"availability": false,
"id": 3
}
]
}
My task is to change the accessibility field. I have a button - change the availability. After clicking it I want to make the availability of a single field changed
changeAvailability = (ailability, id) => {
console.log(ailability)
console.log(id)
const { data } = this.state;
let newData = [...data]
this.setState({
data: newData.map(x => x.id === id ? x.ailability = !ailability : ailability )
})
}
I have created a function that does not work.
My idea was: I click on the element, pass to the element's id and accessibility. In this function, I create a copy of the state. In the right place, I change the availability and I am sending everything to the state.
This function does not work, nothing happens, I have no idea how to fix it
Seems like the map function is returning nothing ...
Could be something like this
changeAvailability = (ailability, id) => {
console.log(ailability)
console.log(id)
const { data } = this.state;
let newData = [...data]
const updateItem = (x) => {
if (x.id === id) {
x.ailability = !ailability;
}
return x;
};
this.setState({
data: newData.map(updateItem)
})
}
I am assuming you want to click on an element which passes an id and availability of an offer, find a matching entry (on the passed id) in the offers array in the state and change the availability of the found object to what you passed in the function.
I recommend using the callback that this.setState() allows you to use, providing a reference to a copy of the previous state, before the setState was called.
changeAvailability = (passedAvail, passedID) => {
this.setState((prevState) => {
// Make a copy of the offers array in the state
const offersArray = prevState.offers
// Find index of matching obj by id
const matchObjIndex = offersArray.findIndex(
(offerObj) => (
offerObj.id === passedID
)
)
// If a match was indeed found
if (matchObjIndex > -1) {
// Find matching obj from index obtained
const matchObj = offersArray[matchObjIndex]
// Change the availibility of the matched obj and obtain a modified obj
const modmatchObj = Object.assign(matchObj, {
availability: passedAvail
})
// Replace the offer obj by index in the offers array.
offersArray.splice(matchObjIndex, 1, modmatchObj);
}
// Finally set state with the updated offers lists
// If no match was found, it will return the old offers array
return {offers: offersArray }
})
}
You can try something like this to make it work:
changeAvailability = (availability, id) =>
this.setState(prevState => {
return {
offers: prevState.offers.map((e, i) =>
e.id !== id ? e : { ...prevState.offers[i], availability }
)
};
});
The issue with your solution is that you shouldn't modify the items you're iterating while doing a map, but you should just return whatever new data you wanna add.

Resources