React Hooks表单验证OnBlur会触发所有字段,而不仅仅是特定字段

时间:2019-09-10 16:31:56

标签: reactjs forms react-hooks onblur

我当前使用的是我几个月前在互联网上某个地方发现的这个惊人的钩子,找不到我找到的文章了,但是基本上我遇到的问题是,当我有一个大表格时,我会选择如果我使用onBlur,则下一个字段将对每个字段运行验证,因此所需的所有字段会自动变为红色并显示错误(因为它们中没有内容,并且我的错误CSS也没有)。

我想等到至少该字段被输入然后再离开该特定字段的验证之前。

基本上,我想知道是否有一种方法可以以某种方式等待直到至少一次选择了输入。我唯一能想到的就是为表单中的每个输入都拥有一个单独的钩子,或者以某种方式将一个事件附加到每个字段上,如果该事件至少被选中一次,则为true / false,但是不知道会如何已实施。

import { useState, useEffect } from "react";

const useFormValidation = (initialState, validate, authenticate) => {
    const [values, setValues] = useState(initialState);
    const [errors, setErrors] = useState({});
    const [isSubmitting, setSubmitting] = useState(false);

    useEffect(() => {
        if (isSubmitting) {
            const noErrors = Object.keys(errors).length === 0;
            if (noErrors) {
                authenticate();
                setSubmitting(false);
            } else {
                setSubmitting(false);
            }
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [errors]);

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



    const handleChangeChecked = (event) => {
        setValues({...values, [event.target.name] : event.target.checked });
    }

    //THIS IS THE FUNCTION I AM TALKING ABOUT
    const handleBlur = () => {
        const validationErrors = validate(values);
        setErrors(validationErrors);
    }

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

    return {
        handleSubmit,
        handleChange,
        handleChangeChecked,
        handleBlur,
        values,
        errors,
        isSubmitting
    };
}

export default useFormValidation;

这也是示例验证,这也是传递给useFormValidation函数的第二个字段。

const validateCareTeam = (values) => {
  let errors = {};

  // First Name Errors
  if (!values.firstName) {
    errors.firstName = "First name is required";
  }

  // Last Name Errors
  if (!values.lastName) {
    errors.lastName = "Last name is required";
  }

// Email Error
if (values.email && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) {
  errors.email = "Please enter a valid email address";
}

const phoneno = /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/
// Phone Number Errors
if (!values.phone) {
  errors.phoneNumber = "Phone number is required";
} else if (!values.phone.match(phoneno)) {
  errors.phoneNumber = "Please enter a phone number with 10 digits.  1 not necessary"
}

  return errors;
}

export default validateCareTeam;

因此,基本上,如果使用姓氏后面的使用标签,则所有其他必填字段-姓氏和电话号码将变为红色。

我希望在单击“提交”按钮之前不进行事件验证,但是我被要求立即进行验证。

2 个答案:

答案 0 :(得分:2)

下面的实现未经过测试,但应了解其工作原理,阅读代码中的注释

import {
  useState,
  useEffect
} from "react";

const useFormValidation = (initialState, validate, authenticate) => {
  const [values, setValues] = useState(initialState);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState([]); // NEW: stores all touched field names
  const [isSubmitting, setSubmitting] = useState(false);

  useEffect(() => {
    if (isSubmitting) {
      const noErrors = Object.keys(errors).length === 0;
      if (noErrors) {
        // NEW: probably need to flush touched
        // setTouched([])
        authenticate();
        setSubmitting(false);
      } else {
        setSubmitting(false);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [errors]);

  const handleChange = (event) => {
    setValues({
      ...values,
      [event.target.name]: event.target.value
    });
    if (!touched.includes(event.target.name)) { // NEW
      setTouched([
        ...touched,
        event.target.name
      ])
    }
  }



  const handleChangeChecked = (event) => {
    setValues({ ...values,
      [event.target.name]: event.target.checked
    });
    if (!touched.includes(event.target.name)) { // NEW: check if we touched this field, then add to touched array
      setTouched([
        ...touched,
        event.target.name
      ])
    }
  }

  //THIS IS THE FUNCTION I AM TALKING ABOUT
  const handleBlur = () => {
    const validationErrors = validate(values);

    const touchedErrors = Object.keys(validationErrors) // NEW
      .filter(key => touched.includes(key)) // get all touched keys
      .reduce((acc, key) => {
        if (!acc[key]) {
          acc[key] = validationErrors[key]
        }
        return acc
      }, {})
    setErrors(touchedErrors); // NEW: touchedErrors has errors that are touched
  }

  const handleSubmit = (event) => {
    event.preventDefault();
    const validationErrors = validate(values);
    // NEW do the same
    const touchedErrors = Object.keys(validationErrors) // NEW
      .filter(key => touched.includes(key)) // get all touched keys
      .reduce((acc, key) => {
        if (!acc[key]) {
          acc[key] = validationErrors[key]
        }
        return acc
      }, {})
    setErrors(touchedErrors); // NEW: touchedErrors has errors that are touched
    setSubmitting(true);
  }

  return {
    handleSubmit,
    handleChange,
    handleChangeChecked,
    handleBlur,
    values,
    touched, // NEW: just extend API
    errors,
    isSubmitting
  };
}

export default useFormValidation;

答案 1 :(得分:0)

@Medet让我与出色的点差运算符一起朝着正确的方向前进,并且非常接近,如果没有上面的代码,我将无法完成它,但是如果用户不输入而进入下一部分,则不会发生任何变化任何if语句的输入实际上都需要在touchedErrors上方的handleBlur函数中。其他更改包括不需要触摸即可返回和提交功能。此外,它不会立即更新触摸,这是此帖子的功能useState set method not reflecting change immediately 基本上,直到输入减少2才运行错误。解决此问题的方法是添加一个新的useEffectHook,该功能在每次触摸时都会更改。

这是工作代码,最终应该使用useCallbacks摆脱禁用eslint的问题,但现在对我有用。

import { useState, useEffect } from "react";

const useFormValidation = (initialState, validate, authenticate) => {
    const [values, setValues] = useState(initialState);
    const [errors, setErrors] = useState({});
    const [touched, setTouched] = useState([]);
    const [isSubmitting, setSubmitting] = useState(false);

    useEffect(() => {
        if (isSubmitting) {
            const noErrors = Object.keys(errors).length === 0;
            if (noErrors) {
                setTouched([]);
                authenticate();
                setSubmitting(false);
            } else {
                setSubmitting(false);
            }
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [errors]);

    // need to rerun after there is a changed to touched
    // this checks to see if there are any errors that should be highlighted
    useEffect(() => {
            const validationErrors = validate(values);
            const touchedErrors = Object.keys(validationErrors)
            .filter(key => touched.includes(key)) // get all touched keys
            .reduce((acc, key) => {
             if (!acc[key]) {
                acc[key] = validationErrors[key]
            }
            return acc
             }, {})
            setErrors(touchedErrors);
        // eslint-disable-next-line react-hooks/exhaustive-deps

    }, [touched, values]);

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

    const handleChangeChecked = (event) => {
        setValues({...values, [event.target.name] : event.target.checked });
        if (!touched.includes(event.target.name)) {
            setTouched([
              ...touched,
              event.target.name
            ])
          }
    }

    const handleBlur = (event) => {
        if (!touched.includes(event.target.name)) {
            setTouched([
              ...touched,
              event.target.name
            ])
          }
    }

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

    return {
        handleSubmit,
        handleChange,
        handleChangeChecked,
        handleBlur,
        values,
        errors,
        isSubmitting
    };
}

export default useFormValidation;