React 表单 useState 对象无法正常工作

React form useState object doesn't work as I want

我的 React 应用程序有问题。我有一个带有两个输入的表单,当我提交带有空输入的表单时,它应该在每个输入中呈现一条错误消息。问题是它不显示第一个输入。我怎样才能修复它以在每个错误中显示错误?实现在 useForm.js.

FormUI图片:

我的代码:

Form.js

const Form = () => {
  
const formLogin = () => {
  console.log("Callback function when form is submitted!");
  console.log("Form Values ", values);
}

const {handleChange, values, errors, handleSubmit} = useForm(formLogin);


  return (
    <Wrapper>
      <form onSubmit={handleSubmit}>

        <div className="govgr-form-group gap-bottom">
          <label className="govgr-label govgr-!-font-weight-bold" htmlFor="code">Code*</label>
          {errors.code && <p className="govgr-error-message"><span className="govgr-visually-hidden">Λάθος:</span>{errors.code}</p>}
          <input className={`govgr-input govgr-!-width-three-quarter ${errors.code ? 'govgr-error-input' : ''}`} id="code" name="code" type="text" onChange={handleChange} />
        </div>

        <fieldset>
          <div className="govgr-form-group">
            <label className="govgr-label govgr-!-font-weight-bold" htmlFor="first">Name*</label>
            {errors.first && <p className="govgr-error-message"><span className="govgr-visually-hidden">Λάθος:</span>{errors.first}</p>}
            <input className={`govgr-input govgr-!-width-three-quarter ${errors.first ? 'govgr-error-input' : ''}`} id="first" name="first" type="text" onChange={handleChange} />
          </div>

         
        </fieldset>

        <button type="submit" className="govgr-btn govgr-btn-primary btn-center">Save</button>

      </form>
    </Wrapper>
  );
};

export default Form;

useForm.js:

const useForm = (callback) => {
  
  const [values, setValues] = useState({});
  
  const [errors, setErrors] = useState({});

  const validate = (event, name, value) => {
    
    event.persist();

    switch (name) {
      case "code":
        if (value.trim() === "" || value.trim() === null) {
          setErrors({
            ...errors,
            code: "Code is required",
          });
        } else {
          let newObj = omit(errors, "code");
          setErrors(newObj);
        }
        break;

      case "first":
        if (value.trim() === "" || value.trim() === null) {
          setErrors({
            ...errors,
            first: "Name is required",
          });
        } else {
          let newObj = omit(errors, "first");
          setErrors(newObj);
        }
        break;

        

      default:
        break;
    }
  };

  
  const handleChange = (event) => {
    event.persist();

    let name = event.target.name;
    let val = event.target.value;

    validate(event, name, val);

    setValues({
      ...values,
      [name]: val,
    });
  };

  const handleSubmit = (event) => {
    if (event) event.preventDefault();

    if (
      Object.keys(errors).length === 0 &&
      Object.keys(values).length !== 0 &&
      values.code &&
      values.first 
    ) {
      callback();
    } else {
      if (!values.code) {
        setErrors({
          ...errors,
          code: "Code is required.",
        });
      }
      if (!values.first) {
        setErrors({
          ...errors,
          first: "Name is required.",
        });
      }
    }
  };

  return {
    values,
    errors,
    handleChange,
    handleSubmit
  };
};

export default useForm;

您可以通过只有一个验证例程来简化您的代码。您可以修复您提到的错误,一次只有一个错误,方法是使用框架传递给 setState 的当前状态。这个的构造是

setState(e => /* whatever you want to return relative to e */);

最终,您看到的错误是因为组件及其代码在每次状态更改后都会重新呈现(但出于性能原因,它们是批处理的),因此您的代码只能看到旧版本的直接访问它时的状态。如果您在更改它时想要实际的当前状态,最好让框架将它直接传递给更改状态函数。如果你一直遵循这个模式,你很少会遇到 React 状态模型的任何问题。

然而,困难的问题不是这些。困难的问题是您消除错误的方式。首先,从 UI 的角度来看,仅在编辑时将字段显示为错误有点不一致,因为表单在首次显示时是错误的。但是,忽略这一点,潜在的技术问题是从对象中删除 属性 。通常我们会在需要这样做时使用数组而不是对象,或者我们会使用下划线的 omit 函数。我在你的代码中看到你调用了一个名为 omit 的函数,但你没有解释它是什么。不过,在您的情况下,从不删除 属性 即可轻松解决此问题。相反,将每个对象都作为具有有效标志的对象。这将允许您完全删除 switch 语句。

您可以更进一步,也可以将值滚动到其中,为每个字段创建一个完整的单一状态,但我不会在这里演示。

我还没有测试过这个,所以这里可能有一两个错别字。

const useForm = (callback) => {
  const [values, setValues] = useState({code: '', first: ''});
  const [errors, setErrors] = useState({first: {message: "Name is required", valid: true}, code: {message: "Code is required", valid: true}});

  const isEmpty = val => val.trim() === "" || val.trim() === null);

  const validate = (name, val) => {
    setErrors(e => ({...e, [name]: {...e[name], valid: isEmpty(val)}}))
    return errors[name].valid;
  };
  
  const handleChange = (event) => {
    let name = event.target.name;
    let val = event.target.value;
    setValues({...values, [name]: val});
    validate(name, val);
  };

  const handleSubmit = event => {
    if (event) event.preventDefault();

    const valid = validate('code', values.code) && validate('first', values.first);

    if ( valid ) {
      callback();
    } 
  };

  return {
    values,
    errors,
    handleChange,
    handleSubmit
  };
};

export default useForm;

而且,您必须稍微更改 HTML 模板:

{!errors.first.valid && <p className="govgr-error-message"><span className="govgr-visually-hidden">Λάθος:</span>{errors.first.message}</p>}

我意识到这个新的 validate 函数与您原来的函数有很大的不同,但根据您自己的代码,我认为您可以处理它。基本上,它只是使用解构和变量 属性 访问器。这看起来在您的理解能力范围内,但如果有任何混淆或实际上不起作用,请随时提出更多问题 :)