1

The useState hook is not working for me. In props.data I am getting the array.

I am trying to update the array of objects, but rendering is not working:

const MenuList = (props)=>{
    
    const [enteredData,setEnteredData] = useState(props.data);
    const amountChangeHandler = (event)=>{
        
        const findElementId = event.target.attributes.alter.value;
        const item = props.data.find(item => item.id === findElementId);
        item.amount = parseInt(document.getElementById(findElementId).value);
        console.log(props.data);
        setEnteredData(props.data);
        
    }
    return(
      <ul>
      {enteredData.map((item)=><li key={item.id} className={classes['cart-item']}>
        <div>
          <h2>{item.mealName}</h2>
          <div className={classes.summary}><i>{item.mealDescription}</i>
            <span className={classes.price}>${item.price}</span>
            <span className={classes.amount}>x {item.amount}</span>
          </div>
        </div>
        <div className={classes.actions}>
          <div><label htmlFor={item.id}>Amount  </label>
          <input type='number' id={item.id} defaultValue='1' ></input>
          </div>
          <button alter={item.id} onClick={amountChangeHandler}>+Add</button>
        </div>
      </li>) }</ul>
    )
}

export default MenuList;
Cassidy
  • 3,328
  • 5
  • 39
  • 76
  • Does this answer your question? [The useState set method is not reflecting a change immediately](https://stackoverflow.com/questions/54069253/the-usestate-set-method-is-not-reflecting-a-change-immediately) – Heretic Monkey Oct 19 '22 at 18:12

2 Answers2

1

This is because react is not identifying that your array has changed. Basically react will assign a reference to the array when you define it. But although you are changing the values inside the array, this reference won't be changed. Because of that component won't be re rendered.

So you have to notify react, that the array has updated.

This can be achieve by updating the enteredData by iterating the props.data

const [enteredData,setEnteredData] = useState(props.data);
const amountChangeHandler = (event)=>{
    
    const findElementId = event.target.attributes.alter.value;
    const item = props.data.find(item => item.id === findElementId);
    item.amount = parseInt(document.getElementById(findElementId).value);
    console.log(props.data);
    setEnteredData([...props.data]);
    
}
return(
  <ul>
  {enteredData.map((item)=><li key={item.id} className={classes['cart-item']}>
    <div>
      <h2>{item.mealName}</h2>
      <div className={classes.summary}><i>{item.mealDescription}</i>
        <span className={classes.price}>${item.price}</span>
        <span className={classes.amount}>x {item.amount}</span>
      </div>
    </div>
    <div className={classes.actions}>
      <div><label htmlFor={item.id}>Amount  </label>
      <input type='number' id={item.id} defaultValue='1' ></input>
      </div>
      <button alter={item.id} onClick={amountChangeHandler}>+Add</button>
    </div>
  </li>) }</ul>
)
Dulaj Ariyaratne
  • 998
  • 1
  • 6
  • 13
  • 1
    Just pointing out that the code in this solution still mutates props at least once the first time, and the previous state each time `amountChangeHandler` is called. – Drew Reese Oct 20 '22 at 00:35
1

Issue

The issue here is state/reference mutation.

// Currently `enteredData` is a reference to `props.data
const [enteredData, setEnteredData] = useState(props.data);

const amountChangeHandler = (event) => {
  const findElementId = event.target.attributes.alter.value;

  // `item`, if found, is a reference to an element in the
  // `enteredData`, a.k.a. `props.data`, array
  const item = props.data.find(item => item.id === findElementId);

  // This is the mutation(!!), directly mutating the item reference
  item.amount = parseInt(document.getElementById(findElementId).value);

  // props.data is still the same reference as `enteredData`
  setEnteredData(props.data);
};
  1. Direct mutation of existing state.
  2. A new array reference is never created for React's reconciliation process to use shallow reference equality to trigger a rerender.

Mutating the props.data array in the child also mutates the source in the parent component, which should be avoided at all costs.

Solution

Use a functional state update to correctly access, and update from, the previous state value. You should shallow copy all state, and nested state, that is being updated.

Example:

const [enteredData, setEnteredData] = useState(props.data);

const amountChangeHandler = (event) => {
  const findElementId = event.target.attributes.alter.value;

  setEnteredData(data => data.map(
    item => item.id === findElementId
    ? {
      ...item,
      amount: parseInt(document.getElementById(findElementId).value, 10)
    }
    : item)
  );
};
Drew Reese
  • 165,259
  • 14
  • 153
  • 181