How to properly update an array inside a react hook state

All we need is an easy explanation of the problem, so here it is.

i’ve been trying to update the object inside an array that respresents a react state, the object should be updated when the value of an input is changed, I could find a way myself to update it, but i’m not sure enough that it is the proper way to do it because when i open the react dev tools and go to the components tab and click the component that i’m working on, the state doesn’t update immidiatly when typing in the input, and in order to see the change i have to click on another component in the dev tool and then go back to the first component and the change is done.

So I’m basically asking if the way i used to update the state is correct and to get some suggestions about better ways to do it so it updates instantly. Thanks

here is the code

the state:

const [items, setItems] = useState([{ name: "", quantity: "", unit: "" }]);

the change handling function (the function that updates the state):

const nameChange = (e, i) => {
  const newItems = items;
  newItems[i].name = e.target.value;
  setItems(newItems);
  console.log(items);
};

the inputs:

{
  items.map((item, i) => {
    return (
      <div key={i} className={`mt3 ${classes.root}`}>
        <TextField
          onChange={e => nameChange(e, i)}
          style={{ width: "30%" }}
          id="standard-basic"
          label="Item name"
        />
        <TextField
          style={{ width: "25%" }}
          id="standard-basic"
          label="quantity"
        />
        <TextField
          style={{ width: "10%" }}
          id="standard-basic"
          label="Unit"
        />
      </div>
    );
  });
}

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

    const [items, setItems] = React.useState({
        name: '',
        quantity: '',
        unit: ''
    });
    const handleChange = prop => event => {
        setItems({ ...items, [prop]: event.target.value });
    };

on your TextFields:

    <TextField onChange={handleChange('name')}/>
    <TextField onChange={handleChange('quantity')}/>

Update: if items is an array:

    const updateItem = (prop, event, index) => {
        const old = items[index];
        const updated = { ...old, [prop]: event.target.value }
        const clone = [...items];
        clone[index] = updated;
        setItems(clone);
    }

on your TextFields:

{items.map((item, i) => (
    <div key={i}>
        <TextField onChange={e => updateItem('name', e, i)}/>
        <TextField onChange={e => updateItem('quantity', e, i)}/>
        <TextField onChange={e => updateItem('unit', e, i)}/>
    </div>
))}

Method 2

I got the issue when state is object. I used redux and mapStateToProps and I can’t listen the property in object change cause react can not check deeper.
Solution: follow code:

// in reducer file
 case 'SAVE_SERVICE_STATE': {
      return {
        ...state,
        service: {...payload},
        loading: false,
      };
    } 

//listen service state change
  useEffect(() => {
    if (props.serviceState) {
      // do something
    }
  }, [props.serviceState]);

hope can help someone.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply