[英]React - How do I manage the state of multiple dynamically created controlled inputs?
因此,就我而言,我正在映射返回的用户对象,并为每个用户创建本质上的表单。 每个表单代表每个用户的“添加工作时间”输入集。
例如,这里是我返回的用户数组的缩写版本:
employees: [
{
name: Jason Doe,
email: jdoe@gmail.com,
},
{
name: Susan Doe,
email: sdoe@gmail.com,
},
{
name: Jon Doe,
email: jdoe@gmail.com
}
]
从这些用户那里,我在 React 组件中为每个用户创建了一个单独的“添加时间”表单,如下所示:
{employees.map((employee, i) => (
<EmployeeTableItem key={i}>
<p>{employee.fullName}</p>
<input type="number" placeholder="Reg. Hours" />
<input type="number" placeholder="OT Hours" />
<input type="date" placeholder="From Date" />
<input type="date" placeholder="To Date" />
<div>
<AddHoursButton bordercolor="secondaryColorLight" type="button">
Add Hours
</AddHoursButton>
</div>
</EmployeeTableItem>
))}
在那个例子中,我还没有设置输入的value
和onChange
,我知道这一点。 我知道如何创建受控输入,但通常当我创建它们时,它们不是来自这样的动态数据集。 在上面的示例中,每个员工都需要自己的状态来管理他们各自的输入,并且需要在员工数据从父组件加载后创建该状态。 本例中的员工数据来自 props。
我的问题是如何管理所有动态输入的状态? 每个输入都需要一个特定的值来保存和设置状态,但是此时不会创建该状态,因为根据传入的数据集,输入都是不同的。
这是我使用 React Hooks 控制输入的典型实现:
状态:
const [postData, setPostData] = useState({
firstName: '',
lastName: '',
email: ''
});
更新功能:
const updatePostData = (value, key) => {
setPostData(prevData => {
return {
...prevData,
[key]: value
};
});
};
输入:
<input value={postData.firstName} onChange={(e) => updatePostData(e.target.value, 'firstName')} />
<input value={postData.lastName} onChange={(e) => updatePostData(e.target.value, 'lastName')} />
<input value={postData.email} onChange={(e) => updatePostData(e.target.value, 'email')} />
这个例子非常简单易行。 我已经知道需要在组件和状态中创建的输入类型。 例如,用户注册表单。 我已经知道我需要什么输入,所以我可以将它们的值硬编码到状态中。
在某种程度上,我倾向于认为这是一个显而易见的事情,我想我已经在考虑如何做到这一点了。 无论如何,我提前感谢您的洞察力和建议!
您可以为每个输入和员工使用一个 id,因此当您将数据设置为 state 时,它会由 id 保存在那里。
employees: [
{
id: 1,
name: Jason Doe,
email: jdoe@gmail.com,
},
{
id: 2,
name: Susan Doe,
email: sdoe@gmail.com,
},
{
id: 3,
name: Jon Doe,
email: jdoe@gmail.com
}]
所以状态将是对象的对象。 您可以尝试将初始状态设置为空对象吗
const [postData, setPostData] = useState({})
传递 id onChange 并使用它来将其设置为状态。
<input id={postData.id} value={postData.firstName} onChange={(e) => updatePostData(e.target.value, postData.id, 'firstName')} />
<input id={postData.id} value={postData.lastName} onChange={(e) => updatePostData(e.target.value, postData.id, 'lastName')} />
<input id={postData.id} value={postData.email} onChange={(e) => updatePostData(e.target.value, postData.id, 'email')} />
更新功能
const updatePostData = (value, key, id) => {
setPostData(prevData => {
return {
...prevData,
...prevData[id] ? prevData[id]: {
...prevData[id]
key: value
} : prevData[id]: { key: value }
};
});
};
我最终通过useEffect()
在挂载时动态初始化postData
状态。 在useEffect()
函数内部,我映射了employees
数组并为每个员工创建了一个对象,其中包含输入所需的属性键。 这是动态创建状态对象的实现:
const AddHours = ({ employees }) => {
const [postData, setPostData] = useState({);
useEffect(() => {
let postDataObj = {};
employees.forEach(employee => {
postDataObj = {
...postDataObj,
[employee._id]: {
regHours: 0,
otHours: 0,
fromDate: 'mm/dd/yy',
toDate: 'mm/dd/yy'
}
};
});
setPostData(postDataObj);
}, [employees]);
}
在forEach
函数中的employees
数组的每次迭代之后,我创建一个带有employee._id
的对象,并在该对象内部设置捕获输入所需的键。 每个employee
对象都可以由每个employee
数据附带的_id
引用。 在每个循环之后,新创建的对象被添加到postDataObj
并保存该对象的先前版本,我将其展开 ( ...postDataObj
) 以获得浅拷贝。
当forEach
函数完成时, postDataObj
通过setPostData(postDataObj)
设置为状态。 此效果将在employees
道具更改时运行。
最后,这就是postData
的console.log
的样子:
5e3db85dfe149131cd7c9c77: {regHours: 0, otHours: 0, fromDate: "mm/dd/yy", toDate: "mm/dd/yy"}
5e3db870fe149131cd7c9c78: {regHours: 0, otHours: 0, fromDate: "mm/dd/yy", toDate: "mm/dd/yy"}
5e3db87ffe149131cd7c9c79: {regHours: 0, otHours: 0, fromDate: "mm/dd/yy", toDate: "mm/dd/yy"}
5e3db896fe149131cd7c9c7a: {regHours: 0, otHours: 0, fromDate: "mm/dd/yy", toDate: "mm/dd/yy"}
5e5d379c03e7d104bb98fa39: {regHours: 0, otHours: 0, fromDate: "mm/dd/yy", toDate: "mm/dd/yy"}
5e5d395503e7d104bb98fa3a: {regHours: 0, otHours: 0, fromDate: "mm/dd/yy", toDate: "mm/dd/yy"}
5e5d3dcd5cd2850882c105ba: {regHours: 0, otHours: 0, fromDate: "mm/dd/yy", toDate: "mm/dd/yy"}
现在我有了处理组件内受控输入所需的对象。
这是输入将用来更新状态的updatePostData
函数:
const updatePostData = (id, key, value) => {
setPostData(prevData => {
return {
...prevData,
[id]: {
...postData[id],
[key]: value
}
};
});
};
这很简单。 输入传入一个id, key, value
参数,该函数将使用该参数在postDataObj
对象中查找employee._id
键,而[key]: value
显然是从输入onChange()
传入的值和目标键onChange()
。 注意prevData
被传播到新对象中,然后重新保存到 state 中。 这对于保持不变的值是必要的,而且我们正在创建状态的新副本,而不是直接改变它。 ...postData[id]
正在传播员工对象的先前键/值。 这会通过更新保留其输入数据对象。
最后,这里是输入:
{ Object.keys(postData).length !== 0 ?
{employees.map((employee, i) => (
<EmployeeTableItem>
<p>{employee.fullName}</p>
<input
value={postData[employee._id].regHours}
onChange={e =>
updatePostData(employee._id, 'regHours', e.target.value)
}
type="number"
placeholder="Reg. Hours"
/>
<input
value={postData[employee._id].otHours}
onChange={e =>
updatePostData(employee._id, 'otHours', e.target.value)
}
type="number"
placeholder="OT Hours"
/>
<input
value={postData[employee._id].fromDate}
onChange={e =>
updatePostData(employee._id, 'fromDate', e.target.value)
}
type="date"
placeholder="From Date"
/>
<input
value={postData[employee._id].toDate}
onChange={e =>
updatePostData(employee._id, 'toDate', e.target.value)
}
type="date"
placeholder="To Date"
/>
<div>
<AddHoursButton
bordercolor="secondaryColorLight"
type="button"
>
Add Hours
</AddHoursButton>
</div>
</EmployeeTableItem>
))} : null
}
由于我们在组件挂载时创建postData
对象,如果我们初始化输入,它们将无法访问最初在挂载时在useEffect()
创建的postData
状态。 为了解决这个问题,只有在通过三元运算符创建postData
状态之后才呈现输入。 尽管postData
的创建速度非常快,但它在组件最初呈现后仍然会稍微发生,因此输入无法立即访问它。
这就是我的意思。 我相信这可以以某种方式变成可重复使用的钩子,但现在这让我进入下一步。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.