ReactJS: replacing object from array with new value replaces whole array - javascript

I have array selectedItems and when I try to update existing object:
i.e. [{lable: "one", uniqueId: 1}, {lable: "two", uniqueId: 1}]
to:
i.e. [{lable: "one", uniqueId: 1}, {lable: "two", uniqueId: 3}]
It replaces the whole array with:
[ { lable: "two", uniqueId: 3 }]
how can I avoid that?
handleChange = (label, uniqueId) => {
const { selectedItems } = this.state
const findExistingItem = selectedItems.find((item) => {
return item.uniqueId === uniqueId;
})
if(findExistingItem) {
selectedItems.splice(findExistingItem);
this.setState(state => ({
selectedItems: [...state.selectedItems, {
label, uniqueId
}]
}))
} else {
this.setState(state => ({
selectedItems: [...state.selectedItems, {
label, uniqueId
}]
}))
}
}

Another approach would be to use the Array#filter and Array#concat functions in tandem, where;
items that don't match on uniqueId are filtered. This substitutes the find() logic of your current solution followed by,
the concat() method being called to append the new "replacement item" to the end of the filtered array
In code that could be achieved like this:
handleChange = (label, uniqueId) => {
const {
selectedItems
} = this.state
this.setState({
selectedItems : selectedItems
/* Filter any existing item matching on uniqueId */
.filter(item => (item.uniqueId !== uniqueId))
/* Append replacement { label, uniqueId } to state array */
.concat([ { label, uniqueId } ])
});
}

Array.find() finds the element in the array and returns its reference.
You can modify the returned object, and then update the state.
Also, you can change the find() condition to find the item by
label or uniqueId based on your needs. To get the output in your
question, you have to find by label not uniqueId.
handleChange = (label, uniqueId) => {
const { selectedItems } = this.state;
const findExistingItem = selectedItems.find((item) => {
// To find by 'label', replace 'uniqueId' below with 'label'.
return item.uniqueId === uniqueId;
})
if(findExistingItem) {
// You are not passing in 'newUniqueId', so only 'label' is updated.
findExistingItem.label = label;
findExistingItem.uniqueId = uniqueId;
}
this.setState(state => ({
selectedItems: selectedItems
}));
}
Live Example:
function handleChange(label, uniqueId) {
const selectedItems = [{label: "one",uniqueId: 1}, {label: "two", uniqueId: 1 }];
const findExistingItem = selectedItems.find(item => {
// Finding by 'label', change to 'uniqueId' if that is what is needed.
return item.label === label;
});
if(findExistingItem) {
findExistingItem.label = label;
findExistingItem.uniqueId = uniqueId;
}
console.log(JSON.stringify(selectedItems));
}
handleChange("two", 3);

If you omit the second argument of .splice (the number of elements to remove), it removes all elements from the index on:
const arr = [1, 2, 3];
arr.splice(0);
console.log(arr);
You might want to only splice 1, and you might want to start at the elements index, not it's value.

You can filter and setState
handleChange = (label, uniqueId) => {
const { selectedItems } = this.state
// filter data if exists other wise return present array
const selectedItem = selectedItems.filter(item => item.uniqueId !== uniqueId)
this.setState({ selectedItems: [...selectedItem, { label, uniqueId }] })
}
Filter slice based on uniqueId an append new if not exists

I think the question is misleading
I think what the OP wants to do is find the label passed in to the function and change the uniqueId of the matching array entry - given the result being [{lable: "two", uniqueId: 3}] would mean that handleChange is being called
handleChange('two', 3);
Given that the logic is more like the following
handleChange = (label, uniqueId) => {
this.setState(state => ({
selectedItems: state.selectedItems.map(item => Object.assign(item, item.lable === label ? {uniqueId} : {}))
});
}
to illustrate this works:
const handleChange = (label, uniqueId) => {
const selectedItems = [{lable: "one", uniqueId: 1}, {lable: "two", uniqueId: 1}];
return selectedItems.map(item => Object.assign(item, item.lable === label ? {uniqueId} : {}))
};
console.log(handleChange('two', 3));
or to "borrow" some nicer code, and FIX it
handleChange = (lable, uniqueId) => {
const { selectedItems } = this.state
const selectedItem = selectedItems.filter(item => item.lable !== lable)
this.setState({ selectedItems: [...selectedItem, { lable, uniqueId }] })
}
Demonstrate it works and produces expected result:
const handleChange = (lable, uniqueId) => {
const selectedItems = [{lable: "one", uniqueId: 1}, {lable: "two", uniqueId: 1}];
const selectedItem = selectedItems.filter(item => item.lable !== lable)
return({ selectedItems: [...selectedItem, { lable, uniqueId }] })
};
console.log(handleChange('two', 3));

Related

react native: passing the value of selected items to a function

I am trying to implement a MultipleSelectList from this library react-native-dropdown-select-list. And I am saving the selected items in the AsyncStorage of #react-native-async-storage/async-storage. I implemented 2 useState variables: first const [selected, setSelected] = useState([{}]); this is a list of {name : muscleGroup, value : id(s) of the exercise(s)} (this is the object that is saved in the list of viewDataList [see setExerciseViewLists ]). The other useState variable that I have implemented is:
const [selectedCount, setSelectedCount] = useState({ // a list of the number of selected exercises per muscleGroup
Back: 0,
Legs: 0,
Chest: 0,
.....
});
The idea: when I call handleSelect (see handleSelect) from my MultipleSelectList I give it 2 parameters (val and item) val should be the id(s) of the exercise(s) because I defined it as this in my MultipleSelectList (see MultipleSelectList) and the item is one list item from viewDataList.
The problem is: val is for some reason, not an ID or a list of IDs, it is an anonymous function:
function (val) {
var temp = _babel_runtime_helpers_toConsumableArray__WEBPACK_IMPORTED_MODULE_0___default()(new
Set([].concat(_babel_runtime_helpers_toConsumableArray__WEBPACK_IMPORTED_MODULE_0___default()(val),
[value])));
return temp;
}
(I don't really understand what this is). Any help would be appriciated.
MultipleSelectList
{viewDataList.map((item, index) => (
<MultipleSelectList
data={item.list.map(listItem => ({
value: listItem.id, //here
}))}
save="value"
setSelected={(val) => handleSelect(val, item)}
selected={selected.map(item => item.value)}
/>
))}
handleSelect/Async saving
const handleSelect = async (val, item) => {
setSelectedCount(prevState => ({
...prevState,
[item.name]: prevState[item.name] + 1, //item.name = muscleGroup
}));
setSelected(prevState => ([
...prevState,
{
name: item.name,
value: val //val should be the ID(s)
},
]));
try {
await AsyncStorage.setItem('selected', JSON.stringify([
...selected,
{
name: item.name,
value: val,
}
]));
await AsyncStorage.setItem('selectedCount', JSON.stringify({
...selectedCount,
[item.name]: selectedCount[item.name] + 1,
}));
} catch (e) {
console.e('Error saving data to AsyncStorage:', e);
}
};
setExerciseViewLists
const setExerciseViewLists = () => {
let list = [];
list.push(
{.....},
{
num: 3,
name: "muscleGroup",
list: [{.....}, { id: "123", exercises : "somthing" }, {.....}]
},
{.....},
);
setViewDataList(list);
};
The issue is not with your data but with the setSelected prop for which you are sending a custom function handleSelect. But if you see the library's documentation, it is clearly mentioned like setSelected is for For Setting the option value which will be stored in your local state. So you can only store the selected values in the local state. The code should be like below inside the component.
const [selectedValues, setSelectedValues] = useState();
return <MultipleSelectList
data={SOME_DATA}
save="value"
setSelected={setSelectedValues}
/>

After adding in Array element change oher element but not adding to array

i've got an array:
dataSet: [
{ name: "Имя1", image: "img.jpeg", author: "Александр Полтавченко", date: "21.02.2020", id: 1 },
{ name: "Имя2", image: "img.png", author: "Александр Полтавченко", date: "21.02.2020", id: 2 },
],
addedToCart: []
and here is the function which put value from dataSet to addedToCart according ID from props:
added = (id) => {
this.setState (( { addedToCart, dataList } )=>{
const newItem = dataList.filter(el=>el.id===id);
const testArr = [...addedToCart ];
const filteredATC = testArr.filter((item, el)=>{
if(addedToCart.indexOf(item)===el){
item.count++
return item, el
}
else {
return item
}
it is works well (only one element with count ++) but if click add to another element it is just change element in array (with correct count surprisingly).
How to put another element into addedToCart, just like
[
{el1},
{el2}
]
filter returns an array instead of the desired element, you should use find instead.
I believe you would desire an approach like this:
added = (id) => {
this.setState (( { addedToCart, dataList } ) => {
const newItem = dataList.find(el=> el.id === id);
const testArr = [...addedToCart ];
const filteredATCIndex = testArr.findIndex((_item, id) => newItem.id === id)
// if there is an added item
if (filteredATCIndex !== -1) {
const count = testArr[filteredATCIndex].count + 1
testArr[filteredATCIndex] = { ...testArr[filteredATCIndex], count }
return { addedToCart: testArr }
}
// for new item
const newItemAdded = { ...newItem, count: 1 }
testArr.push(newItemAdded)
return { addedToCart: testArr }
})
}
though this approach duplicates data, which is not desirable. I suggest you consider to change addedToCart to an object where key value pairs are the id and count respectively from added items. This way you would avoid duplicating data.
then your update state would look like:
added = (id) => {
this.setState (( { addedToCart } ) => {
const count = typeof addedToCart[id] === 'undefined' ? 1 : ++addedToCart[id]
return { addedToCart: { ...addedToCart, [id]: count } }
})
}

React comes back with an unexpected token error when I do a dynamic destructuring of my object

I'm updating an input field with an onChange event in React.
const handleUpdateText = (id, name, text) => {
const newItems = items.map( item => {
if (item.id === id) {
return {...item, [menuLang][name]:text } // <-- error here at ][
}
return item;
} )
setItems(newItems);
}
name and text and name attribute and value of target input field.
menuLang is a state variable (string, eg "en").
The items are something like this:
{
id: 1,
type: "menuitem",
isVisible: true,
en: {
name: "Salad",
desc: "Fresh, seasonal ingredients",
},
price: "10",
},
Without dynamic destructuring it work fine:
const newItem = {...item}
newItem[menuLang][name] = text;
return newItem;
// instead of: return {...item, [menuLang][name]:text }
Any ideas what's the mistake?
use computed property name
let item={id:1,type:"menuitem",isVisible:!0,en:{name:"Salad",desc:"Fresh, seasonal ingredients"},price:"10"};
console.log(item)
let name = 'name'
let text = "Orange"
let menuLang = 'en'
item = {...item,[menuLang]:{...item[menuLang],[name]:text}}
console.log(item)
I don't think there is a mistake here, but I also didn't think what you want to do is possible via destructuring - I think you have to go down the route of your second example here.
const items = [{id:12,name: 'dj' }];
const handleUpdateText = (id = 12, name='ann', text='sample') => {
const newItems = items.map((item) => {
const obj = {...item};
if (item.id === id) {
return {...obj, [menuLang]: {[name]:[text]}};
}
return obj;
});
console.log('item', newItems);
};```

How do i setState with hooks of an object which has multiple arrays ? How to setdebtors here?

I am deleting an one id in an array, how do I setState after filtering it here?
https://codesandbox.io/s/react-example-1m2qn
const Debtors = () => {
const debtors = [
{
id: 1,
name: "John",
relation: "friend",
statement: [
{ id: 1, date: 2010, amount: "1000", purpose: "John" },
{ id: 2, date: 2014, amount: "2000", purpose: "john" }
]
},
,
{
id: 2,
name: "Jack",
relation: "Friend",
statement: [
{ id: 1, date: 2010, amount: "1000", purpose: "jack" },
{ id: 2, date: 2014, amount: "2000", purpose: "jack" }
]
}
];
const [newDebtors, setdebtors] = React.useState(debtors);
const handleDelete = (stat, i) => {
const newList = newDebtors[0].statement.filter(x => x.id !== stat.id);
// How to set debtors here ?
// setdebtors({ ...newDebtors, statement[0]: newList });
console.log(newList)
// How to set debtors here ?
There's two problems:
1) You are iterating off the original debtors object in your render, instead of the newDebtors state you created via useState(), which is why there does not appear to be any UI change.
You need: newDebtors[0].statement.map
2) You need to pass in the item index in your handleDelete() so it knows what item in the array to update. You can have the function do something like this:
In the onClick:
<a
href="javascript:;"
onClick={() => handleDelete(stat, i, 0)}
>
In the handleDelete():
const handleDelete = (stat, i, arrayIndex) => {
const updatedDebtors = newDebtors.map((item, index) => {
if (index === arrayIndex) {
return {
...item,
statement: item.statement.filter(
statement => statement.id !== stat.id
)
};
} else {
return item;
}
});
setDebtors(updatedDebtors);
};
See sandbox for full solution: https://codesandbox.io/s/react-example-x7uoh
You should do it like that:
setdebtors((prevState) => {
let newArray = Array.from(prevState); // Copy the array
// Manipulate the array as you wish
return newArray; // return it
});
The problem is you are mutating the array of "debtors" you need to map through the array of debtors and change any properties in the object.
const handleDelete = (stat, i) => {
const newList = newDebtors.map((debtor, i) => {
if (i === 0) {
debtor.statement = debtor.statement.filter(x => x.id !== stat.id);
}
return debtor;
});
setdebtors(newList);};
An even better approach is to use "useReducer" which is used for mutating more complex pieces of state, like you have here. THe docs are very helpful useReducer
Hmm I dont know what exactly you are trying to do,
Is this what you are looking for?
const handleDelete = (stat, i) => {
const newList = newDebtors[0].statement.filter(x => x.id !== stat.id);
const newFirstItem = {...newDebtors[0],statement: newList}
const newDebtorList = newDebtors.filter(x => x.id !== newFirstItem.id);
newDebtorList.unshift(newFirstItem);
setdebtors(newDebtorList);
}
I know this seems complex but you kinda actually need to do this as you cannot mutate an array in the state...
What I did here is I first created a new statement list(newList), then created a newFirstItem to be set as the new newDebtors[0], then created a new array(newDebtorList) of all the elements of newDebtors except the first one, I modified this array by pushing the newFirstItem to the 0th position(using unshift)
Finally updated the state with this new array...
hope it helps :)
Note: this is for changing the 0th element if you have the id please change the code accordingly

How do I update an array of objects in component state?

I am trying to update the property of an object which is stored in an array.
my state looks something like this:
state = {
todos: [
{
id: '1',
title: 'first item,
completed: false
},
{
id: '2',
title: 'second item,
completed: false
}
],
}
What I am trying to do is access the second element in the 'todos' array and update the completed property to either false -> true or true -> false.
I have a button with the handler for update, and my class method for the update looks like this:
onUpdate = (id) => {
const { todos } = this.state;
let i = todos.findIndex(todo => todo.id === id);
let status = todos[i].completed
let updatedTodo = {
...todos[i],
completed: !status
}
this.setState({
todos: [
...todos.slice(0, i),
updatedTodo,
...todos.slice(i + 1)
]
});
}
While this does work, I want to find out if there is a more concise way of achieving the same result; I tried to use Object.assign(), but that didn't work out because my 'todos' is an array, not an object. Please enlighten me with better code!
It would be best to use update function to make sure you don't work on outdated data:
onUpdate = (id) => {
this.setState(prevState => {
const copy = [...prevState.todos];
const index = copy.findIndex(t => t.id === id);
copy[index].completed = !copy[index].completed;
return { todos: copy }
})
}
You can simply copy your todos from state, then make edits, and after that put it back to the state
onUpdate = (id) => {
var todos = [...this.state.todos]
var target = todos.find(todo => todo.id == id)
if (target) {
target.completed = !target.completed
this.setState({ todos })
}
}

Categories

Resources