119

I have retrieved data stored using useState in an array of object, the data was then outputted into form fields. And now I want to be able to update the fields (state) as I type.

I have seen examples on people updating the state for property in array, but never for state in an array of object, so I don't know how to do it. I've got the index of the object passed to the callback function but I didn't know how to update the state using it.

// sample data structure
const data = [
  {
    id: 1,
    name: 'john',
    gender: 'm'
  }
  {
    id: 2,
    name: 'mary',
    gender: 'f'
  }
]

const [data, setData] = useState([]);

const updateFieldChanged = index => e => {
  console.log('index: ' + index);
  console.log('property name: '+ e.target.name);

  setData() // ??
}

return (
  <React.Fragment>
    {data.map((data, index) => {
      <li key={data.name}>
        <input type="text" name="name" value={data.name} onChange={updateFieldChanged(index)} />
      </li>
    })}
  </React.Fragment>
)
isherwood
  • 58,414
  • 16
  • 114
  • 157
reddy
  • 1,721
  • 3
  • 16
  • 26

13 Answers13

220

Here is how you do it:

// sample data structure
/* const data = [
  {
    id:   1,
    name: 'john',
    gender: 'm'
  }
  {
    id:   2,
    name: 'mary',
    gender: 'f'
  }
] */ // make sure to set the default value in the useState call (I already fixed it)

const [data, setData] = useState([
  {
    id:   1,
    name: 'john',
    gender: 'm'
  }
  {
    id:   2,
    name: 'mary',
    gender: 'f'
  }
]);

const updateFieldChanged = index => e => {
  console.log('index: ' + index);
  console.log('property name: '+ e.target.name);
  let newArr = [...data]; // copying the old datas array
  // a deep copy is not needed as we are overriding the whole object below, and not setting a property of it. this does not mutate the state.
  newArr[index] = e.target.value; // replace e.target.value with whatever you want to change it to

  setData(newArr);
}

return (
  <React.Fragment>
    {data.map((datum, index) => {
      <li key={datum.name}>
        <input type="text" name="name" value={datum.name} onChange={updateFieldChanged(index)}  />
      </li>
    })}
  </React.Fragment>
)
naffetS
  • 3,087
  • 2
  • 14
  • 24
  • 5
    Thanks it kind of works. I can target to any specific property name in the object like this `let propertyName = e.target.name;` `newArr[index][propertyName] = e.target.value;`. But after typing one letter, it didn't stay in focus on the text field, I would have to click on the field again to enter another letter. – reddy May 05 '19 at 01:54
  • That is very strange. I made a fiddle on jsfiddle here and it works fine for me: https://jsfiddle.net/5w9n7cy6/ – naffetS May 05 '19 at 02:38
  • 3
    Okay I figured it out, it was the key in the `
  • ` tag that caused it because it shared the same value as the input field, so when that value changed, the whole
  • refreshed as well. Setting `key={index}` fixed it.
  • – reddy May 05 '19 at 03:25
  • 11
    This mutates the current state. Spread operator only does shallow copy. – vinayr Nov 14 '20 at 14:38
  • 1
    @vinayr In this case, a deep copy is not needed. – naffetS Jan 14 '21 at 04:16
  • FTR, for some reason my text field wasn't updating. I fixed it by simply removing the value prop. – Esteban Vargas Jan 18 '22 at 21:37
  • in large data with paging, deep copy is needed instead of refresh all object, i think so – famfamfam May 09 '23 at 09:34