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

React Hooks Form Validation OnBlur triggers all fields instead of just specific field

我目前正在使用几个月前在互联网上某个地方发现的这个神奇的钩子,但找不到我找到它的文章了,但基本上我遇到的问题是,当我有一个大表格时,我如果我使用 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;

所以基本上,如果在名字之后使用制表符,那么所有其他必填字段 - 姓氏和 phone 号码都会变成红色。

我宁愿在提交按钮被点击之前不事件 运行 验证,但我被要求立即进行验证。

下面的实现没有经过测试,但应该知道它应该如何工作,请阅读代码中的注释

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;

Medet Tleukabiluly 使用出色的传播运算符让我朝着正确的方向前进并且很接近,如果没有上面的代码我无法完成它,但是因为如果用户没有切换到下一部分就不会发生变化将任何输入放入 if 语句实际上需要在 touchedErrors 上方的 handleBlur 函数中。其他更改包括无需触摸即可进入 return 块和提交功能。此外,它不会立即更新触摸,这是 的函数 - 基本上它不会 运行 错误,直到它有 2 个输入。解决此问题的方法是添加一个新的 useEffectHook,每次触摸都会更改 运行s。

这是工作代码,最终应该使用 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;