繁体   English   中英

React - 如何管理多个动态创建的受控输入的状态?

[英]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>
  ))}

在那个例子中,我还没有设置输入的valueonChange ,我知道这一点。 我知道如何创建受控输入,但通常当我创建它们时,它们不是来自这样的动态数据集。 在上面的示例中,每个员工都需要自己的状态来管理他们各自的输入,并且需要在员工数据从父组件加载后创建该状态。 本例中的员工数据来自 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道具更改时运行。

最后,这就是postDataconsole.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM