【问题标题】:Form validation: output of error message表单验证:错误消息的输出
【发布时间】:2018-11-04 17:46:37
【问题描述】:

我使用 React 创建了以下带有验证的表单:

  • 数据应在输入时进行验证
  • 提交前需再次验证数据
  • 所有数据所有字段均为必填且数据有效

这个程序有效,但我有以下问题:
我使用 onBlur 监控数据检查,但是当用户在第一个字段中输入无效数据以及第一个字段的错误消息(“仅字母”)时,会显示第二个字段的错误消息(“此字段是必需”)。

如何改进我的示例:

  1. 在输入时 - 仅当用户触摸特定字段时才会显示错误消息(“此字段是必需的”或针对无效数据的特定消息)
  2. 如果按下“提交”按钮 - 则错误消息应显示在所有包含无效数据的字段附近

我的代码:

const ErrorOutput = ({ error }) => <span>{error}</span>

class FormItem extends React.Component {
  render() {
    return (
      <div>
        <label>
          {this.props.label}
        </label>
        <input
          {...this.props.input}
        />
        {this.props.error && <ErrorOutput error={this.props.error} />}
      </div>
    );
  }
}

class App extends React.Component {
  constructor(props){
    super(props)

    this.state = {
      firstName: '',
      telNo: '',
      submit: false,
      errors: {
        firstName: '',
        telNo: '',
      },
      invalid: false,
    }
  }

  handleSubmit(e){
    e.preventDefault()
    if (this.validate()) {
      console.log('Error!')
    } else {
      console.log('Success!')
    }
  }

  validate = () => {
    const { firstName, telNo } = this.state
    const errors = {}
    let invalid = false;
    if (firstName === '') {
      errors.firstName = 'first name is required'
      invalid = true;
    } else if (!firstName.match(/^[a-zA-Z]+$/)) {
      errors.firstName = 'Letters only'
      invalid = true;
    }  
    if (telNo === '') {
      errors.telNo = 'Phone is required'
      invalid = true;
    } else if (!telNo.match(/^[0-9]+$/)) {
      errors.telNo = 'Numbers only'
      invalid = true;
    }
    this.setState({
      invalid,
      errors,
    })
    
    return invalid;
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit.bind(this)}>
        <FormItem label='First name:' input={{
            type: 'text',
            name: 'firstName',
            value: this.state.firstName,
            onChange: e => this.setState({ firstName: e.target.value }),
            onBlur: () => this.validate(),
          }} error = {this.state.errors.firstName}
        />
        <FormItem label='Phone number:' input={{
            type: 'tel',
            name: 'telNo',
            value: this.state.telNo,
            onChange: e => this.setState({ telNo: e.target.value }),
            onBlur: () => this.validate(),
          }} error = {this.state.errors.telNo}
        />
        <button>
          Submit
        </button> 
      </form>
    )
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<body>
<div id="root"></div>
</body>

【问题讨论】:

  • 如果你为此创建了jsfiddle,它会很容易检查

标签: forms reactjs validation jsx


【解决方案1】:

请从您修改的代码中检查我的示例。我已经尝试简化您的逻辑,使其更具可读性和通用性。

const ErrorOutput = ({ error }) => <span> {error} </span>;

const FormItem = ({ label, input, error }) => (
  <div>
    <label> {label} </label> <input {...input} />
    {error && <ErrorOutput error={error} />}
  </div>
);

class App extends React.Component {
  state = {
    firstName: "",
    telNo: "",
    submit: false,
    errors: {}
  };

  handleSubmit = () => {
    const { firstName, telNo, errors } = this.state;
    const { firstNameError, telNoError } = errors;

    const firstNameIsValid = firstName && !firstNameError;
    const telNoIsValid = telNo && !telNoError;

    firstNameIsValid && telNoIsValid
      ? console.log("Success!")
      : console.log("Error!");
  };

  handleInput = event => {
    this.setState({
      [event.target.name]: event.target.value
    });
  };

  validate = () => {
    const { firstName, telNo } = this.state;

    let errors = {};

    // Name error checking
    switch (true) {
      case !firstName:
        errors.firstNameError = "First name is required";
        break;
      case !firstName.match(/^[a-zA-Z]+$/):
        errors.firstNameError = "First name can have only letters";
        break;
      case firstName.length <= 2:
        errors.firstNameError =
          "First name needs to be at least 2 characters long";
        break;
      default:
        errors.firstNameError = "";
        break;
    }

    switch (true) {
      case !telNo:
        errors.telNoError = "Phone is required";
        break;
      case !telNo.match(/^[0-9]+$/):
        errors.telNoError = "Phone number can have only numbers";
        break;
      case telNo.length <= 8:
        errors.telNoError =
          "Telephone number needs to be at least 8 characters long";
        break;
      default:
        errors.telNoError = "";
        break;
    }

    this.setState({
      errors
    });
  };

  render() {
    const { firstName, telNo, errors } = this.state;
    return (
      <form>
        <FormItem
          label="First name:"
          input={{
            type: "text",
            name: "firstName",
            value: firstName,
            onChange: this.handleInput,
            onBlur: this.validate
          }}
          error={errors.firstNameError}
        />
        <FormItem
          label="Phone number:"
          input={{
            type: "tel",
            name: "telNo",
            value: telNo,
            onChange: this.handleInput,
            onBlur: this.validate
          }}
          error={errors.telNoError}
        />
        <button type="button" onClick={this.handleSubmit}>
          Submit
        </button>
      </form>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

此外,避免在渲染方法中使用 lambda 函数以避免性能瓶颈,因为 lambda 在每次渲染时都会重新生成,并可能导致额外的意外重新渲染。

【讨论】:

  • 谢谢,现在代码实际上更具可读性。但是我仍然无法真正解决,我应该如何从我的问题中解决问题。如何将部分错误检查逻辑切换到formItem组件,防止提交前重复相同的数据验证?
  • @Herasimenak 我将在白天编辑示例以包含它。
  • 在提交之前,您仍然需要进行某种形式的数据验证,即使您将验证每个字段的逻辑移至字段组件。
  • 好的,我已经更新了代码。然后将验证切换到 FormItem 是没有意义的,因为出于提交原因,您仍然需要以某种方式验证父级上的字段。我将验证逻辑留在了 App 组件中,但是提交的验证是不同的。检查更新的 handleSubmit() 方法以获取示例。
【解决方案2】:

您可能需要考虑使用redux-form 来管理整个应用程序的表单状态。即使您不选择使用此库,也值得查看在 Field 包装的输入上定义的 meta props,以帮助阐明您在验证表单时应注意哪些事项。

【讨论】:

    【解决方案3】:

    问题在于,当 onBlur 事件发生时,您的验证方法会尝试验证所有字段。要解决此问题,您可以将事件传递给 validate 方法,然后仅使用

    验证引发事件的字段

    event.target.name

    所以你的验证方法看起来像这样

    validate = e => {
      const { firstName, telNo } = this.state
      const errors = {}
      let invalid = false
      if (e && e.target.name == "firstName") {
        if (firstName === "") {
          errors.firstName = "first name is required"
          invalid = true
        } else if (!firstName.match(/^[a-zA-Z]+$/)) {
          errors.firstName = "Letters only"
          invalid = true
        }
      }
      if(e && e.target.name=="telNo"){
      if (telNo === "") {
        errors.telNo = "Phone is required"
        invalid = true
      } else if (!telNo.match(/^[0-9]+$/)) {
        errors.telNo = "Numbers only"
        invalid = true
      }
    }
      this.setState({
        invalid,
        errors
      })
    
      return invalid
    }
    

    你的 FormItem 看起来像

    <FormItem label='First name:' input={{
                type: 'text',
                name: 'firstName',
                value: this.state.firstName,
                onChange: e => this.setState({ firstName: e.target.value }),
                onBlur: (e) => this.validate(e),
              }} error = {this.state.errors.firstName}
            /> 
    

    【讨论】:

    • 但是在你的情况下,如果我改变输入字段之间的焦点,错误消息只显示在有焦点的输入字段附近,而不显示在两个字段旁边,如果两者都有无效数据
    猜你喜欢
    • 2012-06-04
    • 2021-03-07
    • 1970-01-01
    • 1970-01-01
    • 2017-05-12
    • 2021-11-18
    • 2016-01-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多