I am presenting my user with an editable table pre-populated with data, and want to include a 'Discard Changes' button that will call the handleReset() function to restore the 'active' set of data (an array of objects) to it's original state.
The approach I've taken is to read the data into a variable called 'dataBackup', and also into a state variable called 'dataWorking'. The idea is that user edits will be applied to dataWorking, while dataBackup will be left unchanged and is available for use to reset dataWorking if needed. However when the table is edited, changes are simultaneously applied to both variables!
I'm quite new to React and am not understanding this behavior. I'm also not certain my chosen approach is the best way to handle this functionality, because I haven't been able to find any similar examples out there. Would appreciate some pointers, and have added my sample code belong to demonstrate what's happening.
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 dataBackup = [...budgetData];
const [dataWorking, setDataWorking ] = useState(budgetData);
const handleChange = ( (e, row) => {
let selectedData = [...dataWorking];
const {name, value} = e.target;
selectedData[row][name] = value;
setDataWorking(selectedData);
});
const handleReset = ( (e) => {
setDataWorking(dataBackup);
});
return (
<div>
<table>
<thead>
<tr>
<th>Category</th> <th>Item</th> <th>Amount</th>
</tr>
</thead>
<tbody>
{ dataWorking.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 }}> * budgetData {budgetData.length} * {JSON.stringify(budgetData)} </div>
<div style={{ marginTop: 20, fontSize: 10 }}> * dataWorking {dataWorking.length} * {JSON.stringify(dataWorking)} </div>
<div style={{ marginTop: 20, fontSize: 10 }}> * dataBackup {dataBackup.length} * {JSON.stringify(dataBackup)} </div>
</div>
);
}
export default UpdateBudget;
CodePudding user response:
You are seeing dataBackup
updated after your state change is because you are copying the objects inside the array by reference. If you want to have a totally unreferenced copy of budgetData
, the simplest solution is
would be to use JSON
methods (but I recommend researching what deep copying an object is):
const dataBackup = JSON.parse(JSON.stringify(budgetData))
Generally, best practice to store values that will not cause UI rerenders is by using useRef
:
const dataBackup = React.useRef(JSON.parse(JSON.stringify(budgetData)))
// then access it like that
dataBackup.current
Another thing, inside your handleChange
you are actually mutating the state, which should be avoided:
selectedData[row][name] = value;
Ideally, you should splice and spread those objects so you are not modifying the state directly, or use some deep copy method.