93

I have retrieved datas stored using useState in an array of object, the datas 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 datas = [
  {
    id: 1,
    name: 'john',
    gender: 'm'
  }
  {
    id: 2,
    name: 'mary',
    gender: 'f'
  }
]

const [datas, setDatas] = useState([]);

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

  setData() // ??
}

return (
  <React.Fragment>
    {datas.map((data, index) => {
      <li key={data.name}>
        <input type="text" name="name" value={data.name} onChange={updateFieldChanged(index)} />
      </li>
    })}
  </React.Fragment>
)
Audwin Oyong
  • 2,061
  • 3
  • 10
  • 31
reddy
  • 1,439
  • 2
  • 13
  • 23

9 Answers9

182

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
  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>
)
T3db0t
  • 3,390
  • 4
  • 25
  • 40
Steffan
  • 2,687
  • 1
  • 11
  • 19
  • 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/ – Steffan May 05 '19 at 02:38
  • 2
    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
  • Thank you so much for this. I spent so much time trying to figure this. :) – Ankur Bhatia Mar 31 '20 at 14:39
  • 10
    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. – Steffan 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