How to push item into array of object in array with useState? - arrays

I have an array with objects that also contain array. How can I push element to that array inside object using useState?
const [list, updateList] = useState([defaultList]);
const defaultList = [
{
"name":"Element 1",
"subList": [{"name": "Element 1"}]
}
]
How can I update subList in that case?

If you have one item in your array, you must recreate your array with new/modified data in it.
updateList(state => {
const stateCopy = [...state]
stateCopy[index] = {
...stateCopy[index],
subList: [...state[index].subList, newItem]
}
return stateCopy
})
Update:
If you want to update based on the item name.
updateList(state => {
const stateCopy = [...state]
const indexOfName = stateCopy.findxIndex(item => item.name === name)
stateCopy[indexOfName] = {
...stateCopy[indexOfName],
subList: [...state[indexOfName].subList, newItem]
}
return stateCopy
})

Assuming you want to append an item to the sublist of a specific list item, based on index:
function appendSublistItemForItemAtIndex(newItem, index) {
updateList([
...list,
[index]: {
...list[index],
subList: [
...list[index].subList,
newItem
]
}
])
}
Then call this function wherever needed for your component (some callback, in a useEffect hook, etc.)

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;
});
};

When I try to change one state of an object the other also changes (React)

I'm using an object in two different states, in on of the states I just set the object in the state, and the other state I change one of the values in the object before setting state, but when I make the change both of the states change.
const fetchCategories = () => {
setAllCategory(categoryList1["movies"]);
categoryList1["movies"].forEach((i) => {
setMovies((prev) => {
i["favmovies"] = [];
return [...prev, i];
});
});
};
both states return an empty favmovies array
[
{
"id": "1",
"name": "development",
"image": "img.png",
"favmovies": []
},
{
"id": "2",
"name": "socialmedia",
"image": "img2.png",
"favmovies": []
},
{
"id": "3",
"name": "writing",
"image": "img2.png",
"favmovies": []
}
]
Issue
You are still mutating an object reference:
const fetchCategories = () => {
setAllCategory(categoryList1["movies"]);
categoryList1["movies"].forEach((i) => {
setMovies((prev) => {
i["favmovies"] = []; // <-- mutation is here
return [...prev, i];
});
});
};
i["favmovies"] = []; mutates the i object that is still a reference in the categoryList1.movies array.
Solution
From what I can tell, you want to store categoryList1.movies array into the allCategory state, and then a copy of categoryList1.movies array with the favmovies array emptied/reset to an empty array.
Instead of for-each iterating over the categoryList1.movies array and enqueueing multiple state updates, just map categoryList1.movies array to a new array reference for the movies state.
const fetchCategories = () => {
setAllCategory(categoryList1["movies"]);
setMovies(movies => movies.concat( // <-- add to existing state
categoryList1.movies.map(movie => ({ // <-- map to new array
...movie, // <-- shallow copy movie object
favmovies: [], // <-- update property
}))
));
};

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 update an object element in array with specific index, React State

The code below is what I'm trying to do update main_id and sub_ids in the state.
I got stucked from here...
const [state, setState] = useState({
ids: [
{
main_id: null,
sub_ids: []
}
]
});
//this is what I've tried..
const handleState = index => (v) => {
setState({...setState, ids: setState.ids.map((x,i)=>(
i === index ? {x.main_id: v.id, sub_ids: []})
))})
}
I'm using this function on same component which means it adds in specific index of array with different object.
<componentOne onChange ={handleState(k)}/>
<componentTwo onChange={handlestate(k)} />
The state that I desired to get after this,
state ={
ids:[
{
main_id: v.id,
sub_ids:[]
},
{
main_id: v.id,
sub_ids:[]
}
]
}
Looks like you are trying to spread in your state updater function (setState) versus your current state object (state).
Shallow copy existing state, then also shallow copy the element when the index matches. You should also use a functional state update.
const handleState = index => v => {
setState(state => ({
...state, // <-- copy previous state
ids: state.ids.map((x, i) =>
i === index
? {
...x, // <-- on match, copy element, then update properties
main_id: v.id,
sub_ids: [],
}
: x, // <-- non-match, just pass element through
),
}));
};
It may be a little clearer to simplify your state though since it appears you overwrite the entire element object.
const [ids, setIds] = useState([
{
main_id: null,
sub_ids: []
}
]);
const handleState = index => v => {
setIds(ids => ids.map((x, i) =>
i === index
? {
main_id: v.id,
sub_ids: [],
}
: x,
),
}));
};

Delete multiple item from array - Redux State

I'm working on react app with redux. I want to delete multiple item from array. I write below code in my reducer which delete single item from array but i want to delete multiple item.
case DELETE_LINK:
let dltLink = state.filter(item => {
return item._id !== action.data._id
})
return {
...state,
parentFolderlinks: dltLink
};
It seems you want to filter links from state.parentFolderlinks, say you have the ids in action.data.ids, you could
case DELETE_LINK:
const parentFolderlinks = state.parentFolderlinks.filter(item => {
return !action.data.ids.includes(item._id);
});
return {
...state,
parentFolderlinks
};
On what basis would you like to filter items? I assume that multiple items will not have the same id.
Below example shows how we can filter multiple items in redux. In this case, foods state with items that has type as fruit and removes everything else.
// initial state with all types of foods
const initialState = {
"foods": [
{
name: "apple",
type: "fruit"
},
{
name: "orange",
type: "fruit"
},
{
name: "broccoli",
type: "vegetable"
},
{
name: "spinach",
type: "vegetable"
},
]
}
// sample reducer that shows how to delete multiple items
export default (state = initialState, { type, payload }) => {
switch (type) {
// delete multiple items that does not have type fruit
// i.e both brocolli and spinach are removed because they have type vegetable
case DELETE_ITEMS_WITHOUT_TYPE_FRUIT:
const onlyFruits = state.foods.filter(food => food.type === "fruit");
return {
...state,
foods: onlyFruits
}
}
}
you could map over the state and run it through a function that works out if you want to keep it or not (I don't know what your logic is for that) then return the array at the end
const keepThisItem =(item) => {
return item.keep
}
case DELETE_LINK:
let itemsToKeep = []
let dltLink = state.map(item => {
if(keepThisItem(item){
itemsToKeep.push(item)
}
return itemsToKeep
})

Resources