Home > Mobile >  I think I'm missing something fairly basic about useState() hook in ReactJS?
I think I'm missing something fairly basic about useState() hook in ReactJS?

Time:10-10

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);
});
  • Related