Hello i have problem with update state in React.
const [test, setTest] = useState(
[
{
name: "example1" //i want update single el
},
{
name: "example2" //i want update single el
}
]
)
const updateTest = () => {
setTest([{ ...test[0], name: "example1 changed"}])
}
i want update element without remove another element. For the moment when i click updateTest it cause that my test[1] is removed. Why??
You should do this:
const updateTest = () => {
const newArray = [...test]; // use proper name
newArray[0].name = "new-example1";
setTest(newArray);
}
Anyway, generally you want to map, filter or whatever before updating the state, you should figure it out depending your use case. This one is kind of a test hack
You can replace the element in array and then spread it as a new array or use map in order to overwrite changes with a function.
const [test, setTest] = useState(
[
{
name: "example1" //i want update single el
},
{
name: "example2" //i want update single el
}
]
)
const updateTestSplice = () => {
let temp = ([...test])
temp.splice(0, { name: "example1 changed" })
setTest([...temp])
}
const updateTestMap = () => {
setTest(
test.map(el =>
el.name === "example1"
? { name: "example1 changed" }
: el)
)
}
What you did was you set test to a new array with only one element. If you just want to add a new element to the array, you can do this:
setTest([{ ...test[0], name: "example1 changed"}, ...test]);
and you will end up with an array with both the original elements and the added one.
If, on the other hand, you want to replace an element, you can use splice:
setTest(test.splice(0,1,{ ...test[0], name: "example1 changed"}));
where the first argument(0 in this example) is where you want to start (so in this case at the beginning of the array), next argument (1) is how many existing elements you want to remove, and the last one is the element(s) you want to add to that place in the array.
Related
I've got a counter array, containing 3 objects.
const [counter, setCounter] = useState([
{ id: 0, count: [] },
{ id: 1, count: [] },
{ id: 2, count: [] }
])
Theres then 3 buttons that when pressed call the following function.
const update = (i, text) => {
setCounter(currCount =>
currCount.id === i
? { id: i, count: [...counter[i].count, text] }
: currCount
);
};
The buttons pass "i" which is 0,1,2 corresponding to the 3 object ids and "text" which is the error text.
The function should update the specific object from the array adding the new error text to that id's count array.
I cant seem to get this to work though, it keeps returning undefined.
Any help is appreiciated.
The useState dispatch function (setCounter in your case) replaces the whole state with the value it is provided with.
In your example, you need to recreate the whole array like so:
const update = (i, text) => {
setCounter(currCounter =>
[
...currCounter.filter(count => count.id !== i), // the old counter state without the one we want to change
{ id: i, count: [...currCounter[i].count, text] }
]
);
};
If you have an array as part of your state, and that array contains objects, whats an easy way to update the state with a change to one of those objects?
Example, modified from the tutorial on react:
var CommentBox = React.createClass({
getInitialState: function() {
return {data: [
{ id: 1, author: "john", text: "foo" },
{ id: 2, author: "bob", text: "bar" }
]};
},
handleCommentEdit: function(id, text) {
var existingComment = this.state.data.filter({ function(c) { c.id == id; }).first();
var updatedComments = ??; // not sure how to do this
this.setState({data: updatedComments});
}
}
I quite like doing this with Object.assign rather than the immutability helpers.
handleCommentEdit: function(id, text) {
this.setState({
data: this.state.data.map(el => (el.id === id ? Object.assign({}, el, { text }) : el))
});
}
I just think this is much more succinct than splice and doesn't require knowing an index or explicitly handling the not found case.
If you are feeling all ES2018, you can also do this with spread instead of Object.assign
this.setState({
data: this.state.data.map(el => (el.id === id ? {...el, text} : el))
});
While updating state the key part is to treat it as if it is immutable. Any solution would work fine if you can guarantee it.
Here is my solution using immutability-helper:
jsFiddle:
var update = require('immutability-helper');
handleCommentEdit: function(id, text) {
var data = this.state.data;
var commentIndex = data.findIndex(function(c) {
return c.id == id;
});
var updatedComment = update(data[commentIndex], {text: {$set: text}});
var newData = update(data, {
$splice: [[commentIndex, 1, updatedComment]]
});
this.setState({data: newData});
},
Following questions about state arrays may also help:
Correct modification of state arrays in ReactJS
what is the preferred way to mutate a React state?
I'm trying to explain better how to do this AND what's going on.
First, find the index of the element you're replacing in the state array.
Second, update the element at that index
Third, call setState with the new collection
import update from 'immutability-helper';
// this.state = { employees: [{id: 1, name: 'Obama'}, {id: 2, name: 'Trump'}] }
updateEmployee(employee) {
const index = this.state.employees.findIndex((emp) => emp.id === employee.id);
const updatedEmployees = update(this.state.employees, {$splice: [[index, 1, employee]]}); // array.splice(start, deleteCount, item1)
this.setState({employees: updatedEmployees});
}
Edit: there's a much better way to do this w/o a 3rd party library
const index = this.state.employees.findIndex(emp => emp.id === employee.id);
employees = [...this.state.employees]; // important to create a copy, otherwise you'll modify state outside of setState call
employees[index] = employee;
this.setState({employees});
You can do this with multiple way, I am going to show you that I mostly used. When I am working with arrays in react usually I pass a custom attribute with current index value, in the example below I have passed data-index attribute, data- is html 5 convention.
Ex:
//handleChange method.
handleChange(e){
const {name, value} = e,
index = e.target.getAttribute('data-index'), //custom attribute value
updatedObj = Object.assign({}, this.state.arr[i],{[name]: value});
//update state value.
this.setState({
arr: [
...this.state.arr.slice(0, index),
updatedObj,
...this.state.arr.slice(index + 1)
]
})
}
I have an array like this:
a=[{id:1 , operand:2 ,children:[{a:1]{b:2}]
And I do this when I want to add a new field:
const [items, setItems] = useState(a);
const gettableData = (value) => {
let operanList = items;
let repeatListError = tableData.filter((i) => i.operandId === value.operandId);
if (!repeatListError.length > 0) operanList.push(value);
setTableData(operanList);
};
This method is called every time I click a button.
I want to check operand when I add a new object, if there is only update children.like:
value=[{id:1 , operand:2 ,children:[{a:1]{b:2}{c:3}{d:4}]
I see some inconsistencies in the variable names used in your code, e.g. where is tableData? Is it the items which is part of state?
I don't understand if children is an array of objects or just an object with key value pairs.
Anyway, as I mentioned in my comment, the easiest way to achieve this is using map. Consider it to be a generic implementation and try to use it in your code.
const a=[{id:1 , operand:2 ,children:[{a:1]{b:2}] // What is the data stucture of children? Is it array of objects or just object?
const [items, setItems] = useState(a);
const gettableData = (value) => {
// creating a copy is not required as map will return a new array.
// map => filter + modify, so use map
let updatedList = tableData.map((i) => {
if (i.operandId === value.operandId) {
// match found, so update children
return {
...i, // first, copy everything from i
children: [ // then update children, and since children is an array of objects
...i.children, // first copy every key of i's children
...value.children // then copy value's children
]
}
}
return i; // no match? return i as it is.
});
setItems(updatedItems)
First reformat value like the following.What you have is incorrect in syntax:
const value=[{id:1 , operand:2 ,children:{a:1,b:2,c:3,d:4}}]
This how you can change the children inside the objects that are in your array:
const list = [
{ id: 1, operand: 2, children: { a: 1, b: 2, c: 3, d: 4 } },
{ id: 2, operand: 3, children: { a: 1, b: 2, c: 3, d: 4 } },
];
let newList = [...list];
let updatedChildren = { ...newList[1].children, e: 5, f: 6 };
let updatedItem = { ...newList[1], children: updatedChildren };
newList.splice(1, 1, updatedItem);
console.log(newList);
setState(newList);
you create a new instance of the array. update the children attribute of your desired item ( in this case i chose 1 ). then update the entire object that is in your array ( it contains the id & operand and children ). and finally mutate the newList with replacing the original item by the new one. In the splice method remember the second number must always be one, as it will remove one item and the first number given should be the index of the item that is needed to be changed ( in this case we chose one). then you can continue with setting the state as you wish since you have a new, updated list.
You have to put the array in another variable and tell it that if it has children, it will remember it in the children field and set a new value for it.
I am doing an E-shop template in ReactJS to practice, everything is done but I can't figure out what is the best way to remove one item from state in form of array with objects.
Here is an example of what I am trying to do:
Defining my state:
const [cartData, setCartData] = useState([])
Adding items to it:
const exampleFunction = (heading, price) => {
setCartData([...cartData, {name: heading, price: price}])
}
All of this is working just fine so my state after adding some items looks like this:
[{name: "White T-shirt", price: "$20"}, {name: "Red T-shirt", price: "$20"}]
Now my question is, if user clicks the delete button and I pass to my delete function the name parameter, how do I remove the one item with received name? Here is what I was trying to do but it didn't really work out:
const deleteItem = (name) => {
var newList = []
cartData.map(d => {
if (d.name !== name) {
newList.push(d.name)
}
return d
})
setCartData([])
newList.map(item => {
setCartData([...cartData, {name: item.name}])
})
}
Like I said, I must've done it wrong because this is not doing the job for me. What is the best way to go about deleting one item from state? (please don't try to repair my code if there is better/smarter solution)
Thank you!
What you want is the filter array prototype method.
const deleteItem = (name) => {
setCartData((state) => state.filter((item) => item.name !== name))
}
When using the current state value to update state, it's good practice to pass a callback function to setState.
This should remove the entry with the specified name from cardData:
const deleteItem = (name) => {
const newCartData = cartData.filter((d) => d.name !== name);
setCartData(newCartData);
};
You need to use filter() method of array. According to MDN web docs, the filter() method creates a new array with all elements that pass the test implemented by the provided function.
const deleteItem = (name) => {
const newList = cartData.filter(d => d.name !== name);
setCartData(newList);
}
I'm working on a table planner app where guests can be assigned to dinner tables.
I have created an object array in the state called tabledata, which will contain objects like so:
this.state = {
tabledata: [
{
name: "Top Table",
guests: ["guest1", "guest2", "guest3"]
},
{
name: "Table One",
guests: ["guest3", "guest4", "guest5"]
}
]
}
I am then creating a drag and drop interface where guests can move between tables. I have attempted to update the state like so:
updateTableList (tablename, guest) {
const selectedTableObj = this.state.tabledata.filter((tableObj) => tableObj.name === tablename);
const otherTableObjs = this.state.tabledata.filter((tableObj) => tableObj.name !== tablename);
selectedTableObj[0].guests.push(guest);
const updatedObjectArray = [...otherTableObjs, selectedTableObj];
this.setState({
tabledata: [...otherTableObjs, ...selectedTableObj]
});
}
This works but because I am removing selectedTableObj from the state and then adding it to the end of the array I'm getting some funky results on screen. The updated table always goes to the bottom of the page (as you'd expect).
How can I update the object without changing its position within the array?
Find the index of the table you want to update using Array.findIndex(). Create a new tabledata array. Use Array.slice() to get the items before and after the updated table, and spread them into the new tabledata array. Create a new table object using object spread, add the updated guests array, and add the table object between the previous items:
Code (not tested):
updateTableList(tablename, guest) {
this.setState((prevState) => {
const tableData = prevState.tabledata;
const selectedTableIndex = tableData.findIndex((tableObj) => tableObj.name === tablename);
const updatedTable = tableData[selectedTableIndex];
return {
tabledata: [
...prevState.tabledata.slice(0, selectedTableIndex),
{
...updatedTable,
guests: [...updatedTable.guests, guest]
},
...prevState.tabledata.slice(selectedTableIndex + 1)
]
};
});
}
selectedTableObj[0].guests.push(guest) directly mutates the state which is not encouraged in React.
Try this:
this.setState((prevState) => {
const newData = [...prevState.tabledata];
// if you pass in `index` as argument instead of `tablename` then this will not be needed
const index = prevState.tabledata.findIndex(table => tableObj.name === tablename);
newData[index] = {
...newData[index],
guests: newData[index].guests.concat([guest]),
};
return { tabledata: newData };
});
You also did not remove the guest from its previous table so you need to modify for that.
You can do it with a Array.reduce
let newState = this.state
// let newState = {...this.state} // in case you want everything immutable
newState.tableData = newState.tableData.reduce((acc, table) =>
if(table.name === tableName) {
return acc.concat({...table, guests: table.guests.concat(newGuest)})
} else {
return acc.concat(table)
}
)