I am attempting to add the same array of objects to two different stateful variables using useState(), so that I can allow the user to apply changes to the first, and then discard changes and use the second to revert to the beginning state by selecting a button. I'm new to React and the result is not working out as expected! When the user types in changes, for some reason these are simultaneously applied to both, so that there is now nothing that retains the beginning state and that can be reverted to if the use selects the 'Discard Changes' button.
I've simplified the code to the following example:
import {useState} from 'react';
const budgetData =
[
{ index: 0, category: 'Housing', item: 'Mortgage', amount: 650.99 },
{ index: 1, category: 'Housing', item: 'Insurance', amount: 275.50 },
{ index: 2, category: 'Utilities', item: 'Hydro', amount: 70.00 }
];
function UpdateBudget() {
const backup = budgetData;
const [data, setData ] = useState(budgetData);
const [dataBackup ] = useState(backup);
const handleChange = ( (e, row) => {
let selectedData = [...data];
const {name, value} = e.target;
selectedData[row][name] = value;
setData(selectedData);
});
const handleReset = ( (e) => {
setData(dataBackup);
});
return (
<div>
<table>
<thead>
<tr>
<th>Category</th> <th>Item</th> <th>Amount</th>
</tr>
</thead>
<tbody>
{ data.map( (row) => (
<tr key={row.index}>
<td> <input value={row.category} name="category" onChange={(e) => handleChange(e, row.index)}/> </td>
<td> <input value={row.item } name="item" onChange={(e) => handleChange(e, row.index)}/> </td>
<td> <input value={row.amount } name="amount" onChange={(e) => handleChange(e, row.index)}/> </td>
</tr>
))
}
</tbody>
</table>
<div>
<button onClick={ (e) => handleReset (e)}> Discard Changes </button>
</div>
<div style={{ marginTop: 20, fontSize: 10 }}> * data {data.length} * {JSON.stringify(data)} </div>
<div style={{ marginTop: 20, fontSize: 10 }}> * dataBackup {dataBackup.length} * {JSON.stringify(dataBackup)} </div>
</div>
);
}
Grateful for someone to point me to point to what I'm missing here!
CodePudding user response:
I have a suspicion its because cloning the array doesn't create a copy of the child elements. selectedData[row][name]
this is still referencing the same value, it's a JS quirk.
const budgetData = [
["foo"],
["bar"],
["baz"],
]
const backup = budgetData
const clonedData = [...backup]
clonedData[0][0] = "abc"
console.log(backup) // backup data is mutated
Take a look at lodash cloneDeep
which will also copy child values https://lodash.com/docs/4.17.15#cloneDeep
Also I don't think you need the second state as you're not altering it. You can probably ditch this const [dataBackup ] = useState(backup)
and just use backup
CodePudding user response:
When the user types in changes, for some reason these are simultaneously applied to both,
The reason for that is this code you have:
const handleChange = ( (e, row) => {
let selectedData = [...data];
const {name, value} = e.target;
selectedData[row][name] = value; //-> this line
setData(selectedData);
});
In react you should always update state in immutable way. This is not immutable way to update data
. For that you can use a map
. Example for your case
const handleChange = ((e, row) => {
const {
name,
value
} = e.target;
let selectedData = data.map((x, i) => (i === row ? {
...x,
name: value
} : x))
setData(selectedData);
});