简体   繁体   English

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

[英]React - How do I manage the state of multiple dynamically created controlled inputs?

So in my case, I am mapping over a returned user object, and creating essentially a form for each user.因此,就我而言,我正在映射返回的用户对象,并为每个用户创建本质上的表单。 Each form represents an 'Add Hours Worked' set of inputs for each respective user.每个表单代表每个用户的“添加工作时间”输入集。

For example here is an abbreviated version of my returned user array:例如,这里是我返回的用户数组的缩写版本:

employees: [
    {
      name: Jason Doe,
        email: jdoe@gmail.com,
    },
        {
      name: Susan Doe,
        email: sdoe@gmail.com,
    },
    {
      name: Jon Doe,
        email: jdoe@gmail.com
    }

]

And from these users I create an individual 'Add Hours' form for each of them inside of the React component like so:从这些用户那里,我在 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>
  ))}

In that example I have not yet set the value and onChange of the inputs, I am aware of that.在那个例子中,我还没有设置输入的valueonChange ,我知道这一点。 I do know how to create controlled inputs, but usually when I create them they are not from dynamic sets of data like this.我知道如何创建受控输入,但通常当我创建它们时,它们不是来自这样的动态数据集。 In the above example, each employee would need their own state to manage their respective inputs, and that state needs to be created after the employee data loads in from the parent component.在上面的示例中,每个员工都需要自己的状态来管理他们各自的输入,并且需要在员工数据从父组件加载后创建该状态。 The employee data in this example comes via props.本例中的员工数据来自 props。

My question is how do I manage the state of all of the dynamic inputs?我的问题是如何管理所有动态输入的状态? Each input would need a specific value to hold and set in state, however that state is not created at this point, because the inputs are all different depending on the incoming data set.每个输入都需要一个特定的值来保存和设置状态,但是此时不会创建该状态,因为根据传入的数据集,输入都是不同的。

This is my typical implementation of a controlled input with React Hooks:这是我使用 React Hooks 控制输入的典型实现:

state:状态:

  const [postData, setPostData] = useState({
    firstName: '',
    lastName: '',
    email: ''
  });

update function:更新功能:

  const updatePostData = (value, key) => {
    setPostData(prevData => {
      return {
        ...prevData,
        [key]: value
      };
    });
  };

inputs:输入:

<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')} />

This example is pretty straightforward and simple to do.这个例子非常简单易行。 I already know the types of inputs I would need to create both in the component and in state.我已经知道需要在组件和状态中创建的输入类型。 For example, a user sign up form.例如,用户注册表单。 I already know what inputs I need, so I could hard code the values for them into state.我已经知道我需要什么输入,所以我可以将它们的值硬编码到状态中。

I am leaning towards this being a no-brainer to some degree, and I think I am over thinking how to do this.在某种程度上,我倾向于认为这是一个显而易见的事情,我想我已经在考虑如何做到这一点了。 Regardless, I appreciate the insight and advice in advance!无论如何,我提前感谢您的洞察力和建议!

You can use an id for each input and employee, so when you set data in state it is saved by id there.您可以为每个输入和员工使用一个 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
}]

So state would be object of objects.所以状态将是对象的对象。 Can you try setting your initial state to empty object您可以尝试将初始状态设置为空对象吗

const [postData, setPostData] = useState({})

Pass id onChange and use it to set it into state.传递 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')} />

Update function更新功能

const updatePostData = (value, key, id) => {
  setPostData(prevData => {

    return {
      ...prevData,
      ...prevData[id] ? prevData[id]: {
        ...prevData[id]
        key: value 
      } : prevData[id]: { key: value }
    };
  });
};

I ended up initializing the postData state dynamically on mount via useEffect() .我最终通过useEffect()在挂载时动态初始化postData状态。 Inside of the useEffect() function, I mapped over the employees array and created an object for each employee with the property keys required for the inputs.useEffect()函数内部,我映射了employees数组并为每个员工创建了一个对象,其中包含输入所需的属性键。 Here is that implementation of creating the object in state dynamically:这是动态创建状态对象的实现:

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

After each iteration of the employees array in the forEach function, I create an object with the employee._id and inside of that object, setup the keys required to capture the inputs.forEach函数中的employees数组的每次迭代之后,我创建一个带有employee._id的对象,并在该对象内部设置捕获输入所需的键。 Each employee object can be referenced by the _id that comes along with each employee data.每个employee对象都可以由每个employee数据附带的_id引用。 After every loop, the newly created object, is added to postDataObj and to hold the previous version of that object, I spread it ( ...postDataObj ) to get a shallow copy.在每个循环之后,新创建的对象被添加到postDataObj并保存该对象的先前版本,我将其展开 ( ...postDataObj ) 以获得浅拷贝。

When the forEach function is completed, postDataObj is set into state via setPostData(postDataObj) .forEach函数完成时, postDataObj通过setPostData(postDataObj)设置为状态。 This effect will be ran anytime the employees props change.此效果将在employees道具更改时运行。

Finally, this is what a console.log of postData looks like after all of this:最后,这就是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"}

Now I have the the objects needed to handle the controlled inputs within the component.现在我有了处理组件内受控输入所需的对象。

This is the updatePostData function that the inputs will utilize to update state:这是输入将用来更新状态的updatePostData函数:

  const updatePostData = (id, key, value) => {
    setPostData(prevData => {
      return {
        ...prevData,
        [id]: {
          ...postData[id],
          [key]: value
        }
      };
    });
  };

This is pretty straightforward.这很简单。 The inputs pass in an id, key, value argument, that the function will use to find the employee._id key in the postDataObj object, and the [key]: value is obviously the value and target key passed in from the input onChange() .输入传入一个id, key, value参数,该函数将使用该参数在postDataObj对象中查找employee._id键,而[key]: value显然是从输入onChange()传入的值和目标键onChange() Note that the prevData is spread into the new object, and then re-saved into state.注意prevData被传播到新对象中,然后重新保存到 state 中。 This is necessary to persist the unchanged values, but also we are creating a new copy of the state, and not mutating it directly.这对于保持不变的值是必要的,而且我们正在创建状态的新副本,而不是直接改变它。 The ...postData[id] is spreading the previous key/values of the employee's object. ...postData[id]正在传播员工对象的先前键/值。 This persists their input data object through updates.这会通过更新保留其输入数据对象。

Finally, here are the inputs:最后,这里是输入:

{ 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 
}

Since we are creating the postData object when the component mounts, if we initialize the inputs, they won't have access to the postData state created in the useEffect() initially on mount.由于我们在组件挂载时创建postData对象,如果我们初始化输入,它们将无法访问最初在挂载时在useEffect()创建的postData状态。 To get around this, the inputs are only rendered after the postData state is created via a ternary operator.为了解决这个问题,只有在通过三元运算符创建postData状态之后才呈现输入。 Although the postData is created very fast, it still happens slightly after the component renders initially, so therefore the inputs don't get access to it right off the bat.尽管postData的创建速度非常快,但它在组件最初呈现后仍然会稍微发生,因此输入无法立即访问它。

And that's about that for me on this.这就是我的意思。 I am sure this could somehow be turned in a reusable hook in some way, but for now this gets me to the next step.我相信这可以以某种方式变成可重复使用的钩子,但现在这让我进入下一步。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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