父母如何使用Formik了解儿童表格列表的状态?

发布于 2025-01-20 06:52:25 字数 4244 浏览 2 评论 0原文

我有一份表格,其中包含建筑材料列表,这些材料可以是已注册用于建筑的材料和新材料:

材质列表

MaterialBar

function MaterialBar({
  materialBarId,
  material,
  onDelete,
  onChange,
}: MaterialBarProps) {
  const { values, handleChange } = useFormik<MaterialBar>({
    initialValues: material,
    onSubmit: console.log,
    enableReinitialize: true,
  });

  const updateField = (event) => {
      handleChange(event);
      onChange(materialBarId, values);
  };

  return (
    <DropdownWrapper>
      <Dropdown
        label="Material"
        items={/* A list of available materials */}
        selectedItem={values.material}
        onSelect={updateField}
      />
      .... Other fields
      <TextField
        name="amount"
        id="material-amount"
        label="Amount"
        type="number"
        onChange={updateField}
      />
      <DeleteButton onClick={() => onDelete(materialBarId)}>
        <img src={remove} />
      </DeleteButton>
    </DropdownWrapper>
  );
}

父级(ConstructionMaterials):

function ConstructionMaterials({
  project,
  materials,
  attachMaterial,
}: ProjectMaterialsProps) {
  const [allMaterials, setAllMaterials] = useState<IProjectMaterial[]>(
    getProjectMaterialsFrom(project.materials)
  );

  const saveAllMaterials = () => {
    allMaterials.forEach((newMaterial) => {
      if (newMaterial.isNew) {
        attachMaterial(newMaterial);
      }
    });
  };

  const updateNewMaterial = (
    materialBarId: number,
    updatedMaterial: MaterialBar
  ): void => {
    const updatedList: IProjectMaterial[] = allMaterials.map((material) => {
      if (material.projectMaterialId === materialBarId) {
        return {
          ...material,
          materialId: materials.find(
            (currentMaterial) =>
              currentMaterial.name === updatedMaterial.material
          )?.id,
          type: updatedMaterial.type,
          length: updatedMaterial.length,
          width: updatedMaterial.width,
          value: updatedMaterial.amount,
        };
      }
      return material;
    });
    setAllMaterials(updatedList);
  };

  // Adds a new empty material to the list
  const addMaterial = (): void => {
    setAllMaterials([
      ...allMaterials,
      {
        projectMaterialId: calculateMaterialBarId(),
        projectId: project.id,
        isNew: true,
      },
    ]);
  };

  return (
    <>
      {allMaterials.map((material) => {
        const materialBar: MaterialBar = {
          material: material.name || "",
          type: material.type || "",
          amount: material.value || 0,
          length: material.length || 0,
          width: material.width || 0,
        };
        return (
          <AddMaterialBar
            key={material.projectMaterialId}
            materialBarId={material.projectMaterialId!}
            materials={materials}
            onDelete={removeMaterial}
            onChange={updateNewMaterial}
          />
        );
      })}
      <Button onClick={() => saveAllMaterials()}>
        {texts.BUTTON_SAVE_MATERIALS}
      </Button>
    </>
  );
}

我很难弄清楚如何管理材料列表。我在 MaterialBar 中使用 Formik(useFormik 挂钩)来获取关心每个字段的值。

我的挑战是如何保持所有数据干净并在组件之间轻松传递数据,同时了解哪些材料是新的,哪些材料已经存在。如果我只在 MaterialBar 中使用 Formik,那么 ConstructionMaterials 不知道每个字段中所做的更改,并且它需要更新的数据,因为它使用“save”调用后端all”操作(图像中的“保存”按钮不应该出现,但它们是我的临时修复)。

为了避免这个问题,我还使用 MaterialBar 上的 onChange 来跟踪 ConstructionMaterials 中的每种材质,但这似乎是多余的,因为这就是 Formik应该照顾。我还在材质类型中添加了一个 isNew 字段来跟踪它是否是新的,因此我没有现有材质和新材质的两个列表。

我已经查看了 ConstructionMaterials 中的 FieldArray,但不应该'是否可以在子级中使用 Formik,因为父级应该只获取数据并将其发送到 API?

那么:是否有一种聪明的方法来处理项目列表,其中父级可以了解子级表单中所做的更改,以便发出批量创建请求,而父级不必还跟踪子级中的所有对象?

很抱歉这篇文章很长,但我不知道如何在不失去上下文的情况下缩短它。

I have a form consisting of a list of building materials which can be materials already registered for the construction and new materials:

Materials list

The MaterialBar:

function MaterialBar({
  materialBarId,
  material,
  onDelete,
  onChange,
}: MaterialBarProps) {
  const { values, handleChange } = useFormik<MaterialBar>({
    initialValues: material,
    onSubmit: console.log,
    enableReinitialize: true,
  });

  const updateField = (event) => {
      handleChange(event);
      onChange(materialBarId, values);
  };

  return (
    <DropdownWrapper>
      <Dropdown
        label="Material"
        items={/* A list of available materials */}
        selectedItem={values.material}
        onSelect={updateField}
      />
      .... Other fields
      <TextField
        name="amount"
        id="material-amount"
        label="Amount"
        type="number"
        onChange={updateField}
      />
      <DeleteButton onClick={() => onDelete(materialBarId)}>
        <img src={remove} />
      </DeleteButton>
    </DropdownWrapper>
  );
}

The parent (ConstructionMaterials):

function ConstructionMaterials({
  project,
  materials,
  attachMaterial,
}: ProjectMaterialsProps) {
  const [allMaterials, setAllMaterials] = useState<IProjectMaterial[]>(
    getProjectMaterialsFrom(project.materials)
  );

  const saveAllMaterials = () => {
    allMaterials.forEach((newMaterial) => {
      if (newMaterial.isNew) {
        attachMaterial(newMaterial);
      }
    });
  };

  const updateNewMaterial = (
    materialBarId: number,
    updatedMaterial: MaterialBar
  ): void => {
    const updatedList: IProjectMaterial[] = allMaterials.map((material) => {
      if (material.projectMaterialId === materialBarId) {
        return {
          ...material,
          materialId: materials.find(
            (currentMaterial) =>
              currentMaterial.name === updatedMaterial.material
          )?.id,
          type: updatedMaterial.type,
          length: updatedMaterial.length,
          width: updatedMaterial.width,
          value: updatedMaterial.amount,
        };
      }
      return material;
    });
    setAllMaterials(updatedList);
  };

  // Adds a new empty material to the list
  const addMaterial = (): void => {
    setAllMaterials([
      ...allMaterials,
      {
        projectMaterialId: calculateMaterialBarId(),
        projectId: project.id,
        isNew: true,
      },
    ]);
  };

  return (
    <>
      {allMaterials.map((material) => {
        const materialBar: MaterialBar = {
          material: material.name || "",
          type: material.type || "",
          amount: material.value || 0,
          length: material.length || 0,
          width: material.width || 0,
        };
        return (
          <AddMaterialBar
            key={material.projectMaterialId}
            materialBarId={material.projectMaterialId!}
            materials={materials}
            onDelete={removeMaterial}
            onChange={updateNewMaterial}
          />
        );
      })}
      <Button onClick={() => saveAllMaterials()}>
        {texts.BUTTON_SAVE_MATERIALS}
      </Button>
    </>
  );
}

I have a hard time figuring out how to manage the list of materials. I use Formik (the useFormik hook) in the MaterialBar to take care of the values of each field.

My challenge is how to keep all the data clean and easily pass it between the components while knowing which materials are new and which already exist. If I just use Formik in the MaterialBar, then ConstructionMaterials does not know about the changes made in each field and it needs the updated data because it calls the backend with a "save all" action (the "Save"-buttons in the image should not be there, but they are my temporary fix).

To circumvent this, I also keep track of each material in ConstructionMaterials with the onChange on MaterialBar, but that seems redundant, since this is what Formik should take care of. I have also added a isNew field to the material type to keep track of whether it is new, so I don't two lists for existing and new materials.

I have had a look at FieldArray in ConstructionMaterials, but shouldn't Formik be used in the child, since the parent should just grab the data and send it to the API?

So: is there a clever way to handle a list of items where the parent can know about the changes made in the childs form, to make a bulk create request without the parent having to also keep track of all the objects in the children?

Sorry about the long post, but I don't know how to make it shorter without loosing the context.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文