How do I update states `onChange` in an array of object in React Hooks

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>
)

Solution 1:

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>
)

Solution 2:

You can do this without mutation by mapping your old array into a new one, swapping what you want to change for an updated item along the way.

setDatas(
    datas.map(item => 
        item.id === index 
        ? {...item, someProp : "changed"} 
        : item 
))

Solution 3:

You don't even need to be using the index ( except for the key if you want ) nor copying the old datas array,and can even do it inline or just pass data as an argument if you prefer updateFieldChanged to not be inline. It's done very quickly that way :

  const initial_data = [
    {
      id: 1,
      name: "john",
      gender: "m",
    },
    {
      id: 2,
      name: "mary",
      gender: "f",
    },
  ];

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

  return (
    <div>
      {datas.map((data, index) => (
        <li key={index}>
          <input
            type="text"
            value={data.name}
            onChange={(e) => {
              data.name = e.target.value;
              setDatas([...datas]);
            }}
          />
        </li>
      ))}
    </div>
  );
};