如何在 React Hooks 中更新对象数组中的“ onChange”状态

我已经检索了使用 useState存储在对象数组中的数据,然后将数据输出到表单字段中。现在我希望能够在输入时更新字段(状态)。

我见过一些例子,人们在数组中更新属性的状态,但是从来没有在对象的数组中更新状态,所以我不知道怎么做。我已经得到了传递给回调函数的对象的索引,但是我不知道如何使用它来更新状态。

// 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>
)
227465 次浏览

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

The accepted answer leads the developer into significant risk that they will mutate the source sequence, as witnessed in comments:

let newArr = [...data];
// oops! newArr[index] is in both newArr and data
// this might cause nasty bugs in React.
newArr[index][propertyName] = e.target.value;

This will mean that, in some cases, React does not pick up and render the changes.

The idiomatic way of doing this is 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
))

This is what I do:

const [datas, setDatas] = useState([
{
id: 1,
name: "john",
gender: "m",
},
{
id: 2,
name: "mary",
gender: "f",
},
]);


const updateFieldChanged = (name, index) => (event) => {
let newArr = datas.map((item, i) => {
if (index == i) {
return { ...item, [name]: event.target.value };
} else {
return item;
}
});
setDatas(newArr);
};


return (
<React.Fragment>
{datas.map((data, index) => {
<li key={data.name}>
<input
type="text"
name="name"
value={data.name}
onChange={updateFieldChanged("name", index)}
/>
</li>;
<li key={data.gender}>
<input
type="text"
name="gender"
value={data.gender}
onChange={updateFieldChanged("gender", index)}
/>
</li>;
})}
</React.Fragment>
);

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

Base on @Steffan, thus use as your way:

const [arr,arrSet] = useState(array_value);
...
let newArr = [...arr];
arr.map((data,index) => {
newArr[index].somename= new_value;
});
arrSet(newArr);

Use useEffect to check new arr value.

setDatas(datas=>({
...datas,
[index]: e.target.value
}))

with index being the target position and e.target.value the new value

A little late to the party, but it is an option to spread the contents of the array in a new object, then replacing the desired object in the selected index and finally producing an array from that result, it is a short answer, probably not the best for large arrays.

// data = [{name: 'tom', age: 15, etc...}, {name: 'jerry', age: 10, etc...}]
// index = 1
setData(Object.values({...data, [index]: {...data[index], name: 'the mouse' }}))
// data = [{name: 'tom', age: 15, etc...}, {name: 'the mouse', age: 10, etc...}]

Spread the array before that. As you cannot update the hook directly without using the method returned by useState

const newState = [...originalState]
newState[index] = newValue
setOriginalState(newState)

This will modify the value of the state and update the useState hook if its an array of string.

const updateFieldChanged = index => e => {
   

name=e.target.name //key
  

let newArr = [...data]; // copying the old datas array
newArr[index][name] = e.target.value; //key and value
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>
)

I am late to reply but I had also same problem, so I got solution through this query. Have a look on it if it can help you.The example that I did is that I have a state , named OrderList, and so a setter for it is SetOrderList. To update a specific record in a list, am using SetOrderList and passing it a map function with that list of which I need to change, so I will compare Index or Id of my list, where it will match, I will change that specific record.

const Action = (Id, Status) => { //`enter code here`Call this function onChange or onClick event
setorderList([...orderList.map((order) =>
order.id === Id ? { ...order, status: Status } : order
),
]);
}

complete example for update value based on index and generate input based on for loop....

import React, { useState,useEffect } from "react";


export default function App() {
const [datas, setDatas] =useState([])


useEffect(() => {
console.log("datas");
console.log(datas);
}, [datas]);


const onchangeInput = (val, index) =>{
setDatas(datas=>({
...datas,
[index]: val.target.value
}))
console.log(datas);
}


return (
<>




{
(() => {
const inputs = [];
for (let i = 0; i < 20; i++){
console.log(i);
inputs.push(<input key={i} onChange={(val)=>{onchangeInput(val,i)}} />);
}
return inputs;
})()
}
  

</>
);
}

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

const updateFieldChanged = (index, e) => { const updateData = { ...data[index], name: e.target.name }

setData([...data.slice(0, index), updateData, ...data.slice(index + 1)]) }

const MyCount = () =>{
const myData = [
{
id: 1,
name: 'john',
gender: 'm'
},
{
id: 2,
name: 'mary',
gender: 'f'
}
]
const [count, setCount] = useState(0);
const [datas, setDatas] = useState(myData);


const clkBtn = () =>{
setCount((c) => c + 1);
}


  



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