【问题标题】:Check if input field is changed检查输入字段是否已更改
【发布时间】:2021-02-27 14:56:05
【问题描述】:

我正在使用 React 制作步进表单。表格结构也差不多完成了..

有两个步骤,

-> Basic Details

-> Employment Details

这里使用了一个表单上下文来填写输入字段的默认值,并且如果进行了任何更改,则通过状态存储。

form-context.js

import React, { useState } from 'react';

export const FormContext = React.createContext();

export function FormProvider({ children }) {
  const [formValue, setFormValue] = useState({
    basicDetails: {
      firstName: 'John',
      lastName: '',
    },
    companyDetails: {
      companyName: 'Some Company',
      designation: 'Some Designation',
    },
  });

  return (
    <FormContext.Provider value={[formValue, setFormValue]}>
      {children}
    </FormContext.Provider>
  );
}

这里我有下一个和上一个按钮可以在步骤之间移动,

index.js:

  const next = () => setCurrentPage((prev) => prev + 1);
  const prev = () => setCurrentPage((prev) => prev - 1);

要求:

->点击next/previous按钮,我需要检查是否有任何输入改变。

-> 通过这个我可以调用 API 来保存在我的实际应用程序中单击下一步按钮时所做的更改。

-> 在这里,如果你帮我做一些console log 那就绰绰有余了。

例如:

-> 如果用户将基本详细信息部分中的名字从John 修改为Doe,则单击下一步按钮可以控制台记录基本详细信息发生了变化。

-> 如果基本细节部分中没有任何字段更改,则可以直接进行下一步(就像现在一样)。

注意:请不要对任何输入名称进行硬编码,因为我每一步都有超过 30 个输入字段..

【问题讨论】:

    标签: javascript reactjs forms next.js stepper


    【解决方案1】:

    我认为这里的主要问题是您当前的实现替换了现有状态,每次更改,因此不可能知道是否/发生了什么变化。甚至用于保存前一个状态值的“usePrevious”钩子在这里都不起作用,因为它只会保存上一个字段编辑,而不是原始值,以进行比较。许多表单处理解决方案通过保持未变异的初始状态或跟踪“脏”字段和/或更多来处理此问题。

    这是我的一个建议,可以调整您的表单上下文状态以跟踪已更新的字段。 dirtyFields 对象保存原始值。

    表单提供者

    const [formValue, setFormValue] = useState({
      basicDetails: {
        fields: {
          firstName: 'John',
          lastName: '',
        },
        dirtyFields: {},
      },
      companyDetails: {
        fields: {
          companyName: 'Some Company',
          designation: 'Some Designation',
        },
        dirtyFields: {},
      },
    });
    

    基本细节

    const BasicDetails = () => {
      const [value, setValue] = React.useContext(FormContext);
      const {
        basicDetails: { fields }, // <-- get field values
      } = value;
    
      const handleInputChange = (event) => {
        const { name, value } = event.target;
    
        setValue((prev) => ({
          ...prev,
          basicDetails: {
            ...prev.basicDetails,
            fields: {
              ...prev.basicDetails.fields,
              [name]: value,
            },
            ...(prev.basicDetails.dirtyFields[name] // <-- only mark dirty once with original value
              ? {}
              : {
                  dirtyFields: {
                    ...prev.basicDetails.dirtyFields,
                    [name]: prev.basicDetails.fields[name],
                  },
                }),
          },
        }));
      };
    
      return (
        <>
          <div className="form-group col-sm-6">
            <label htmlFor="firstName">First Name</label>
            <input
              ...
              value={fields.firstName} // <-- access fields object
              ...
            />
          </div>
          <div className="form-group col-sm-4">
            <label htmlFor="lastName">Last Name</label>
            <input
              ...
              value={fields.lastName}
              ...
            />
          </div>
        </>
      );
    };
    

    就业详情

    const EmploymentDetails = () => {
      const [value, setValue] = React.useContext(FormContext);
      const {
        companyDetails: { fields },
      } = value;
    
      const handleInputChange = (event) => {
        const { name, value } = event.target;
    
        setValue((prev) => ({
          ...prev,
          companyDetails: {
            ...prev.companyDetails,
            fields: {
              ...prev.companyDetails.fields,
              [name]: value,
            },
            ...(prev.companyDetails.dirtyFields[name]
              ? {}
              : {
                  dirtyFields: {
                    ...prev.companyDetails.dirtyFields,
                    [name]: prev.companyDetails.fields[name],
                  },
                }),
          },
        }));
      };
    
      return (
        <>
          <div className="form-group col-sm-6">
            <label htmlFor="companyName">Company Name</label>
            <input
              ...
              value={fields.companyName}
              ...
            />
          </div>
          <div className="form-group col-sm-4">
            <label htmlFor="designation">Designation</label>
            <input
              ...
              value={fields.designation}
              ...
            />
          </div>
        </>
      );
    };
    

    在递增/递减步长时检查脏字段。

    为每个部分指定一个与表单上下文中的“表单键”匹配的 id。

    const sections = [
      {
        title: 'Basic Details',
        id: 'basicDetails',
        onClick: () => setCurrentPage(1),
      },
      {
        title: 'Employment Details',
        id: 'companyDetails',
        onClick: () => setCurrentPage(2),
      },
      { title: 'Review', id: 'review', onClick: () => setCurrentPage(3) },
    ];
    

    创建一个checkDirty 实用程序。这里我只是简单的记录下脏字段

    const checkDirty = (page) => {
      console.log('check dirty', 'page', page);
      console.log(
        value[sections[page - 1].id] && value[sections[page - 1].id].dirtyFields,
      );
    };
    
    const next = () => {
      setCurrentPage((prev) => prev + 1);
      checkDirty(currentPage); // <-- check for dirty fields when updating page step
    };
    
    const prev = () => {
      setCurrentPage((prev) => prev - 1);
      checkDirty(currentPage);
    };
    

    由于表单上下文中存在额外的嵌套状态,这里有一个实用程序可以将其还原为只是您希望在审阅步骤中呈现的表单数据。

    const prettyReview = sections.reduce(
      (sections, section) => ({
        ...sections,
        ...(value[section.id]
          ? { [section.id]: { ...value[section.id].fields } }
          : {}),
      }),
      {},
    );
    
    ...
    
    <pre>{JSON.stringify(prettyReview, null, 2)}</pre>
    

    编辑

    您说您的数据来自后端 API 调用。这是一个上下文状态初始化函数,它将 API 数据形状映射到我拥有的状态形状。

    来自 API 的给定数据

    const apiData = {
      basicDetails: {
        firstName: 'John',
        lastName: '',
      },
      companyDetails: {
        companyName: 'Some Company',
        designation: 'Some Designation',
      },
    };
    

    初始化函数

    const initializeContext = (data) =>
      Object.entries(data).reduce(
        (sections, [section, fields]) => ({
          ...sections,
          [section]: {
            fields,
            dirtyFields: {},
          },
        }),
        {},
      );
    

    初始化FormProvider上下文状态

    function FormProvider({ children }) {
      const [formValue, setFormValue] = useState(initializeContext(apiData));
    
      return (
        <FormContext.Provider value={[formValue, setFormValue]}>
          {children}
        </FormContext.Provider>
      );
    }
    

    【讨论】:

    • 感谢您的详细回答.. 我会接受您的解决方案.. 您是否有关于相同实现的代码框要分享?
    • @Undefined 我以为我没有,但我已经将它保存在我的帐户中,所以我一定是打算并且只是忽略了在我的答案中添加一个链接。我现在已经添加了。
    • 哦,现在我明白了为什么它在我的实际应用程序中不起作用。在我的应用程序中,表单的这些 JSON 值来自后端 API,所以我不能像你那样修改 JSON 值。 . 你对这里有什么帮助吗?抱歉,我没有指出那里是我的错误。因为认为解决方案是直截了当的..
    • @Undefined 啊,我明白了……好吧,您可以从后端数据对象创建一个新对象,基本上将其浅拷贝到新形状中并添加“脏”属性。假设您的后端数据是您问题的当前形状,我可以在明天早上使用映射函数更新我的答案(如果您需要它)。
    • 是的,Drew,请在早上之前添加您的新解决方案。我也会尝试实施该解决方案。真的,您非常有帮助并感谢它..
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-27
    • 1970-01-01
    • 2010-11-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多