1

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!

Giorgi Moniava
  • 27,046
  • 9
  • 53
  • 90

1 Answers1

0

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

const budgetData = [
  ["foo"],
  ["bar"],
  ["baz"],
]
const backup = budgetData

const clonedData = _.cloneDeep(backup)

clonedData[0][0] = "abc"

console.log(backup) // backup data is not mutated
console.log(clonedData) // cloned data holds the changes
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

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

Lex
  • 4,749
  • 3
  • 45
  • 66
  • @KeithArnold take a look a the link to `cloneDeep` from the lodash library. A util like that is required to make a copy of an object. There are meany ways to do it https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript I updated the answer with an example. JS spread operator doesn't create copies of the array elements, so they still get overridden when assigned. – Lex Oct 17 '21 at 15:41
  • Many thanks Lex for pointing me to the source of my problem, and yes lodash .cloneDeep is the way to go for my situation (lots of dates in the data). I now understand the difference between shallow and deep cloning and it's great to be moving forward again! – Keith Arnold Oct 18 '21 at 00:32