React自定义验证钩子不正确工作

huangapple go评论73阅读模式
英文:

React custom hook for validation not working correctly

问题

I understand that you want a translation of the provided text excluding the code sections. Here's the translation of the non-code parts:

我正在使用React处理一个表单,所以我创建了一个自定义钩子来处理表单和验证,目前它运行良好。但是最近我发现了一些问题,对于多个表单来说并不适用。假设有一个登录表单,然后当我登录后,还有其他几个表单,这种情况下并不适用。

我所做的

  • 我的输入字段组件如下:
<form onSubmit={handleSubmit}>
  <input
    type="text"
    className={`${errors.fName && "input-error"}`}
    name="fName"
    id="fName"
    onChange={handleChange}
    value={values.fName || ""}
  />;
</form>
  • 我编写了一个useForm自定义钩子,用于处理更改、提交和验证,以下是代码:
import { useState, useEffect } from "react";
const useForm = (callback, validate) => {
  const [values, setValues] = useState({});
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  useEffect(() => {
    if (Object.keys(errors).length === 0 && isSubmitting) {
      callback();
    }
  }, [errors]);

  const handleSubmit = (event) => {
    if (event) event.preventDefault();
    setErrors(validate(values));
    setIsSubmitting(true);
  };

  const handleChange = (event) => {
    event.persist();
    setValues((values) => ({
      ...values,
      [event.target.name]: event.target.value,
    }));
  };

  return {
    handleChange,
    handleSubmit,
    values,
    errors,
  };
};
export default useForm;
  • 现在以下是我的validation.js文件:
export default function validate(values) {
  let errors = {};
  if (!values.emailAddress) {
    errors.emailAddress = "电子邮件地址是必填项";
  }

  if (!values.password) {
    errors.password = "密码是必填项";
  }
  if (!values.fName) {
    errors.fName = "名字是必填项";
  }
  if (!values.lName) {
    errors.lName = "姓氏是必填项";
  }
  return errors;
}
  • 现在在我的主要组件中,我在声明输入字段时调用useform钩子,并传递了点击和验证函数,点击函数仅在验证函数之后才会起作用。以下是我如何做的:
const { values, errors, handleChange, handleSubmit } = useForm(
  btnClick,
  Validate
);

重现问题的步骤

  • 在验证文件中,我有四个验证规则,分别用于电子邮件地址密码名字姓氏
  • 现在电子邮件地址密码在一个表单中,而名字姓氏在另一个表单中。
  • 因此,当我点击表单一中的提交按钮时,它不会给我values,因为对于这个表单来说,表单二中仍然存在错误。
  • 我的想法是,无论我点击哪个表单,它都应该仅验证该表单的规则,而不验证其他表单的规则。

我正在寻找的

  • 如果可以使用类似https://react-hook-form.com/中的useRef/ref来实现,即没有onchange,如果我不想给任何输入添加验证,我只需不传递ref
  • 我喜欢@TusharShahi的答案,将遵循那个答案,但我正在寻找更多通用的代码选项。
英文:

I am working with a form in react, So I created custom hook for handling form and validation, which is working fine, But I recently found some issue in that, which not good for more than one form, suppose one form is login form, and then when I login I have several other forms as well which doesn't work in this scenario.

What I did

  • My Input field component

    &lt;form onSubmit={handleSubmit}&gt;
    &lt;input
        type=&quot;text&quot;
        className={`${errors.fName &amp;&amp; &quot;input-error&quot;}`}
        name=&quot;fName&quot;
        id=&quot;fName&quot;
        onChange={handleChange}
        value={values.fName || &quot;&quot;}
      /&gt;;
    &lt;/form&gt;
    
  • I have written one useForm custom hook, to handle the changes, submit and validation as well, below is the code

    import { useState, useEffect } from &quot;react&quot;;
    const useForm = (callback, validate) =&gt; {
    const [values, setValues] = useState({});
    const [errors, setErrors] = useState({});
    const [isSubmitting, setIsSubmitting] = useState(false);
    
    useEffect(() =&gt; {
      if (Object.keys(errors).length === 0 &amp;&amp; isSubmitting) {
        callback();
      }
    }, [errors]);
    
    const handleSubmit = (event) =&gt; {
      if (event) event.preventDefault();
      setErrors(validate(values));
      setIsSubmitting(true);
    };
    
    const handleChange = (event) =&gt; {
      event.persist();
      setValues((values) =&gt; ({
        ...values,
        [event.target.name]: event.target.value,
      }));
    };
    
    return {
      handleChange,
      handleSubmit,
      values,
      errors,
    };
    };
    export default useForm;
    
  • Now below is my validation.js file

    export default function validate(values) {
    let errors = {};
    if (!values.emailAddress) {
      errors.emailAddress = &quot;Email address is required&quot;;
    }
    
    if (!values.password) {
      errors.password = &quot;Password is required&quot;;
    }
    if (!values.fName) {
      errors.fName = &quot;first name is required&quot;;
    }
    if (!values.lName) {
      errors.lName = &quot;last name is required&quot;;
    }
    return errors;
    

    }

  • Now In my main component where Input fields are declarer I am calling the useform hook and passing the click and validate function, and click function will only work after validate function. Below is how I am doing

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

Steps to recreate the issue

  • In validation file I have four validate rules which are for email address, password, firstname, lastname
  • Now email address and password are in one form and firstname and lastname are in other form
  • So when I click submit in form one it doesn't give me the values because for this form two errors still exist
  • My thinkin is whichever form I am click submit it should validate those rules only not other form rules

What I am looking for

  • If I can do it with useRef/ref like how https://react-hook-form.com/ is doing where there is no onchange, and if want not to give validation to any of input I will just not pass ref

  • I like @TusharShahi answer and will follow that, But I am looking for more options which is more of generic code

答案1

得分: 0

你还没有展示完整的示例,但听起来你正在在两个表单之间共享相同的状态。

Hooks 每次调用时都会创建新的状态变量。因此,如果你想要两个具有独立状态的表单,你需要两次调用该 hook:

  const {
    values: values1,
    errors: errors1,
    handleChange: handleChange1,
    handleSubmit: handleSubmit1,
  } = useForm(btnClick, validate);

  const {
    values: values2,
    errors: errors2,
    handleChange: handleChange2,
    handleSubmit: handleSubmit2,
  } = useForm(btnClick, validate);

然后你可以为每个表单分配它们各自的属性。

注意:我不知道 btnClick 是什么,所以不清楚你是否需要两个副本。

如果有什么不清楚的地方,请展示包含两个表单的完整示例。

英文:

You haven't shown the full example, but it sounds like you're sharing the same state between two forms.

Hooks create new state variables each time they are called. So if you want two forms with separate state, you need to call the hook twice:

  const {
    values: values1,
    errors: errors1,
    handleChange: handleChange1,
    handleSubmit: handleSubmit1,
  } = useForm(btnClick, validate);

  const {
    values: values2,
    errors: errors2,
    handleChange: handleChange2,
    handleSubmit: handleSubmit2,
  } = useForm(btnClick, validate);

Then you can give each form it's respective properties.

Note: I don't know what btnClick is, so no idea if you need two copies of that.

If something doesn't make sense, then please show the full example with both forms.

答案2

得分: -1

以下是您要翻译的内容:

这肯定可以使用引用(refs)来解决,稍微修改一下validate函数。

虽然可以使用单个validate函数来完成,但请记住需要向函数传递要验证的字段值。没有这些信息,它将不知道要检查哪些值,并且将检查所有值。

这是我如何修改validate函数的方式:

const errMap = {
  emailAddress: "Email address is required",
  password: "Password is required",
  fName: "first name is required",
  lName: "last name is required"
};

function validate(values, namesToCheck) {
  let errors = {};

  for (let i = 0; i < namesToCheck.length; i++) {
    if (!values[namesToCheck[i]]) {
      errors[namesToCheck[i]] = errMap[namesToCheck[i]];
    }
  }
  return errors;
}

errMap只是一个简单的配置对象,用于告诉每个字段值的错误消息。键是字段的名称,值是错误消息。

由于我们的解决方案需要足够灵活,以适应不需要验证单个表单中的所有字段的情况,因此钩子也需要更改。它需要允许传递需要检查的字段。

const useForm = (callback, validate, inputsToCheck) => {
  const [values, setValues] = useState({});
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  useEffect(() => {
    if (Object.keys(errors).length === 0 && isSubmitting) {
      callback();
    }
  }, [errors]);

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

    let namesToCheck = inputsToCheck.map(({ current: { name } = {} }) => name);

    setErrors(validate(values, namesToCheck));
    setIsSubmitting(true);
  };

  const handleChange = (event) => {
    event.persist();
    setValues((values) => ({
      ...values,
      [event.target.name]: event.target.value
    }));
  };

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

有两个更改:

  1. 添加了一个新参数,其中包含要检查的输入的所有引用。
  2. 修改了handleSubmit以提取需要传递给validate的字段名称。

最后,将正确的引用传递给钩子将完成解决方案。

英文:

So this can definitely be solved using refs and modifying the validate function a bit.

While this can be done by using a single validate function, do remember it is necessary to pass to the function what field values it should be validating. Without that it would not be aware of which values to check and it would be checking for all the values.

This is how I would modify the validate function:

const errMap = {
  emailAddress: &quot;Email address is required&quot;,
  password: &quot;Password is required&quot;,

  fName: &quot;first name is required&quot;,
  lName: &quot;last name is required&quot;
};

function validate(values, namesToCheck) {
  let errors = {};

  for (let i = 0; i &lt; namesToCheck.length; i++) {
    if (!values[namesToCheck[i]]) {
      errors[namesToCheck[i]] = errMap[namesToCheck[i]];
    }
  }
  return errors;
}

errMap is just an easy config object to tell what each field value's error would look like. The key is the name of the field and the value is the error message.

Since our solution need to be extensible enough to the situation when not all fields in a single form need to be validated, the hook should also change. It needs to allow passing only the fields which need to be checked.

const useForm = (callback, validate, inputsToCheck) =&gt; {
  const [values, setValues] = useState({});
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  useEffect(() =&gt; {
    if (Object.keys(errors).length === 0 &amp;&amp; isSubmitting) {
      callback();
    }
  }, [errors]);

  const handleSubmit = (event) =&gt; {
    if (event) event.preventDefault();

    let namesToCheck = inputsToCheck.map(({ current: { name } = {} }) =&gt; name);

    setErrors(validate(values, namesToCheck));
    setIsSubmitting(true);
  };

  const handleChange = (event) =&gt; {
    event.persist();
    setValues((values) =&gt; ({
      ...values,
      [event.target.name]: event.target.value
    }));
  };

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

There are two changes:

  1. A new param has been added which contains all the refs of the inputs to check.
  2. handleSubmit is modified to extract names that need to pass to validate.

Finally, passing the correct refs to the hook will complete the solution.

const Form2 = () =&gt; {
  const btn2Click = () =&gt; {
    console.log(&quot;form2&quot;, values);
  };
  const fnameRef = useRef();
  const lnameRef = useRef();

  const { values, errors, handleChange, handleSubmit } = useForm(
    btn2Click,
    validate,
    [fnameRef, lnameRef]
  );
  return (
    &lt;div&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;div className=&quot;row&quot;&gt;
          &lt;div className=&quot;input-group&quot;&gt;
            &lt;label&gt;First name&lt;/label&gt;
            &lt;input
              ref={fnameRef}
              type=&quot;text&quot;
              className={`${errors.fName &amp;&amp; &quot;input-error&quot;}`}
              name=&quot;fName&quot;
              id=&quot;fName&quot;
              onChange={handleChange}
              value={values.fName || &quot;&quot;}
            /&gt;
            &lt;p className=&quot;error-text&quot;&gt;{errors.fName}&lt;/p&gt;
          &lt;/div&gt;

          &lt;div className=&quot;input-group&quot;&gt;
            &lt;label&gt;last name&lt;/label&gt;
            &lt;input
              ref={lnameRef}
              type=&quot;text&quot;
              className={`${errors.lName &amp;&amp; &quot;input-error&quot;}`}
              name=&quot;lName&quot;
              id=&quot;lName&quot;
              onChange={handleChange}
              value={values.lName || &quot;&quot;}
            /&gt;
            &lt;p className=&quot;error-text&quot;&gt;{errors.lName}&lt;/p&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;button type=&quot;submit&quot; className=&quot;btn&quot;&gt;
          Click 2
        &lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
};

codesandbox link

PS:
I do not see any problem in creating two validate functions. Both can reside in the same file too. And then you can pass the correct one when invoking your hook. With the individual validate functions you need not pass the list of inputs which need to validated as your individual validate function would be catered to your form fields. I would also like to suggest that your individual validate methods should reside close to your forms. Form1 with Validate1 (in a separate file if needed).

huangapple
  • 本文由 发表于 2023年4月17日 18:20:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/76034100.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定