【问题标题】:How to properly validate input values with React.JS?如何使用 React.JS 正确验证输入值?
【发布时间】:2014-07-24 00:58:26
【问题描述】:

我有一个简单的表格。所有组件和状态都保存在 Page 组件中。有 2 个显示标题和 3 个输入字段。第一个输入应该是文本,第二个和第三个应该是整数。当用户输入错误类型的数据时,我希望在输入字段旁边弹出一条错误消息。我的问题与 React.JS 中的最佳实践有关

谁决定该值是否有效?我想输入字段的唯一工作就是将值引导回持有状态的组件,那么这是否意味着只有 Page 才能确定值是否有效?

然后我应该如何让弹出窗口出现? Page 是否必须触发一个新的布尔状态元素,该元素将通过 perp 传递,告诉 Adaptive_Input 显示错误消息?

JSFiddle

JS:

/**
 * @jsx React.DOM
 */
var Adaptive_Input = React.createClass({ 
    handle_change: function(){
        var new_text = this.refs.input.getDOMNode().value;
        this.props.on_Input_Change(new_text);
    },
    render: function(){
        return (
                <div className='adaptive_placeholder_input_container'>
                    <input 
                        className="adaptive_input"
                        type="text" 
                        required="required" 
                        onChange= {this.handle_change}
                        ref="input"
                    ></input>
                    <label
                        className="adaptive_placeholder"
                        alt={this.props.initial}
                        placeholder={this.props.focused}
                    ></label>
                </div>              
                );
    }
});

var Form = React.createClass({
    render: function(){
        return (
                <form>
                    <Adaptive_Input
                        initial={'Name Input'}
                        focused={'Name Input'}
                        on_Input_Change={this.props.handle_text_input}
                    />
                    <Adaptive_Input
                        initial={'Value 1'}
                        focused={'Value 1'}
                        on_Input_Change={this.props.handle_value_1_input}
                    />
                    <Adaptive_Input
                        initial={'Value 2'}
                        focused={'Value 2'}
                        on_Input_Change={this.props.handle_value_2_input}
                    />
                </form>
                );
    }
});

var Page = React.createClass({
    getInitialState: function(){
        return {
            Name : "No Name",
            Value_1 : '0',
            Value_2 : '0',
            Display_Value: '0'
        };
    },
    handle_text_input: function(new_text){
        this.setState({
                Name: new_text
            });
    },
    handle_value_1_input: function(new_value){
        console.log("===");
        var updated_display = parseInt(new_value) + parseInt(this.state.Value_2);
        updated_display = updated_display.toString();
        this.setState({
                Display_Value: updated_display 
            });
    },
    handle_value_2_input: function(new_value){
        var updated_display = parseInt(this.state.Value_1) + parseInt(new_value);
        updated_display = updated_display.toString();
        this.setState({
                Display_Value: updated_display
            });
    },
    render: function(){
        return(
                <div>
                    <h2>{this.state.Name}</h2>
                    <h2>Value 1 + Value 2 = {this.state.Display_Value}</h2>
                    <Form
                        handle_text_input={this.handle_text_input}
                        handle_value_1_input = {this.handle_value_1_input}
                        handle_value_2_input = {this.handle_value_2_input}
                    />
                </div>
        );
    }
});

React.renderComponent(<Page />, document.body);

【问题讨论】:

标签: javascript validation input reactjs


【解决方案1】:

有时您的应用程序中可以有多个具有类似验证的字段。在这种情况下,我建议您创建公共组件字段来保存此验证。

例如,假设您在应用程序的几个位置强制输入文本。您可以创建一个 TextInput 组件:

constructor(props) {
    super(props); 
    this.state = {
        touched: false, error: '', class: '', value: ''
    }
}

onValueChanged = (event) => {
    let [error, validClass, value] = ["", "", event.target.value];

    [error, validClass] = (!value && this.props.required) ? 
        ["Value cannot be empty", "is-invalid"] : ["", "is-valid"]

    this.props.onChange({value: value, error: error});

    this.setState({
        touched: true,
        error: error,
        class: validClass,
        value: value
    })
}

render() {
    return (
        <div>
            <input type="text"
                value={this.props.value}
                onChange={this.onValueChanged}
                className={"form-control " + this.state.class}
                id="{this.props.id}"
                placeholder={this.props.placeholder} />
            {this.state.error ?
                <div className="invalid-feedback">
                    {this.state.error}
                </div> : null
            }
        </div>
    )
}

然后您就可以在应用程序的任何地方使用这样的组件:

constructor(props) {
    super(props);
    this.state = {
        user: {firstName: '', lastName: ''},
        formState: {
            firstName: { error: '' },
            lastName: { error: '' }
        }
    }
}

onFirstNameChange = (model) => {
    let user = this.state.user;
    user.firstName = model.value;

    this.setState({
        user: user,
        formState: {...this.state.formState, firstName: { error: model.error }}
    })
}

onLastNameChange = (model) => {
    let user = this.state.user;
    user.lastName = model.value;

    this.setState({
        user: user,
        formState: {...this.state.formState, lastName: { error: model.error }}
    })
}


onSubmit = (e) => {
   // submit logic
}


render() {
    return (
        <form onSubmit={this.onSubmit}>
            <TextInput id="input_firstName"
                value={this.state.user.firstName}
                onChange={this.onFirstNameChange}
                required = {true}
                placeholder="First name" />

            <TextInput id="input_lastName"
                value={this.state.user.lastName}
                onChange={this.onLastNameChange}
                required = {true}
                placeholder="Last name" />

            {this.state.formState.firstName.error || this.state.formState.lastName.error ?
                <button type="submit" disabled className="btn btn-primary margin-left disabled">Save</button>
                : <button type="submit" className="btn btn-primary margin-left">Save</button>
            }

        </form>
    )
}

好处:

  • 您无需重复验证逻辑
  • 表单中的代码更少 - 更具可读性
  • 其他常用的输入逻辑可以保留在组件中
  • 您遵循 React 规则,即组件应尽可能愚蠢

参考。 https://webfellas.tech/#/article/5

【讨论】:

    【解决方案2】:

    我过去使用过 redux-form 和 formik,最近 React 引入了 Hook,我已经为它构建了一个自定义的 hook。请检查一下,看看它是否使您的表单验证更容易。

    Github:https://github.com/bluebill1049/react-hook-form

    网址:http://react-hook-form.now.sh

    使用这种方法,您也不再进行受控输入。

    下面的例子:

    import React from 'react'
    import useForm from 'react-hook-form'
    
    function App() {
      const { register, handleSubmit, errors } = useForm() // initialise the hook
      const onSubmit = (data) => { console.log(data) } // callback when validation pass
    
      return (
        <form onSubmit={handleSubmit(onSubmit)}>
          <input name="firstname" ref={register} /> {/* register an input */}
    
          <input name="lastname" ref={register({ required: true })} /> {/* apply required validation */}
          {errors.lastname && 'Last name is required.'} {/* error message */}
    
          <input name="age" ref={register({ pattern: /\d+/ })} /> {/* apply a Refex validation */}
          {errors.age && 'Please enter number for age.'} {/* error message */}
    
          <input type="submit" />
        </form>
      )
    }
    

    【讨论】:

      【解决方案3】:

      在输入文本字段及其下方使用 onChange={this.handleChange.bind(this, "name") 方法和 value={this.state.fields["name"]} 创建 span 元素以显示错误,请参见下面的示例。

      export default class Form extends Component {
      
        constructor(){
          super()
          this.state ={
             fields: {
               name:'',
               email: '',
               message: ''
             },
             errors: {},
             disabled : false
          }
        }
      
        handleValidation(){
             let fields = this.state.fields;
             let errors = {};
             let formIsValid = true;
      
             if(!fields["name"]){
                formIsValid = false;
                errors["name"] = "Name field cannot be empty";
             }
      
             if(typeof fields["name"] !== "undefined" && !fields["name"] === false){
                if(!fields["name"].match(/^[a-zA-Z]+$/)){
                   formIsValid = false;
                   errors["name"] = "Only letters";
                }
             }
      
             if(!fields["email"]){
                formIsValid = false;
                errors["email"] = "Email field cannot be empty";
             }
      
             if(typeof fields["email"] !== "undefined" && !fields["email"] === false){
                let lastAtPos = fields["email"].lastIndexOf('@');
                let lastDotPos = fields["email"].lastIndexOf('.');
      
                if (!(lastAtPos < lastDotPos && lastAtPos > 0 && fields["email"].indexOf('@@') === -1 && lastDotPos > 2 && (fields["email"].length - lastDotPos) > 2)) {
                   formIsValid = false;
                   errors["email"] = "Email is not valid";
                 }
            }
      
            if(!fields["message"]){
               formIsValid = false;
               errors["message"] = " Message field cannot be empty";
            }
      
            this.setState({errors: errors});
            return formIsValid;
        }
      
        handleChange(field, e){
            let fields = this.state.fields;
            fields[field] = e.target.value;
            this.setState({fields});
        }
      
        handleSubmit(e){
            e.preventDefault();
            if(this.handleValidation()){
                console.log('validation successful')
              }else{
                console.log('validation failed')
              }
        }
      
        render(){
          return (
            <form onSubmit={this.handleSubmit.bind(this)} method="POST">
                <div className="row">
                  <div className="col-25">
                      <label htmlFor="name">Name</label>
                  </div>
                  <div className="col-75">
                      <input type="text" placeholder="Enter Name"  refs="name" onChange={this.handleChange.bind(this, "name")} value={this.state.fields["name"]}/>
                      <span style={{color: "red"}}>{this.state.errors["name"]}</span>
                  </div>
                </div>
                <div className="row">
                  <div className="col-25">
                    <label htmlFor="exampleInputEmail1">Email address</label>
                  </div>
                  <div className="col-75">
                      <input type="email" placeholder="Enter Email" refs="email" aria-describedby="emailHelp" onChange={this.handleChange.bind(this, "email")} value={this.state.fields["email"]}/>
                      <span style={{color: "red"}}>{this.state.errors["email"]}</span>
                  </div>
                </div>
                <div className="row">
                  <div className="col-25">
                      <label htmlFor="message">Message</label>
                  </div>
                  <div className="col-75">
                      <textarea type="text" placeholder="Enter Message" rows="5" refs="message" onChange={this.handleChange.bind(this, "message")} value={this.state.fields["message"]}></textarea>
                      <span style={{color: "red"}}>{this.state.errors["message"]}</span>
                  </div>
                </div>
                <div className="row">
                  <button type="submit" disabled={this.state.disabled}>{this.state.disabled ? 'Sending...' : 'Send'}</button>
                </div>
            </form>
          )
        }
      }
      

      【讨论】:

        【解决方案4】:

        你的 jsfiddle 不再工作了。 我已经修复它:http://jsfiddle.net/tkrotoff/bgC6E/40/ 使用 React 16 和 ES6 类。

        class Adaptive_Input extends React.Component {
          handle_change(e) {
            var new_text = e.currentTarget.value;
            this.props.on_Input_Change(new_text);
          }
        
          render() {
            return (
              <div className="adaptive_placeholder_input_container">
                <input
                  className="adaptive_input"
                  type="text"
                  required="required"
                  onChange={this.handle_change.bind(this)} />
                <label
                  className="adaptive_placeholder"
                  alt={this.props.initial}
                  placeholder={this.props.focused} />
              </div>
            );
          }
        }
        
        class Form extends React.Component {
          render() {
            return (
              <form>
                <Adaptive_Input
                  initial={'Name Input'}
                  focused={'Name Input'}
                  on_Input_Change={this.props.handle_text_input} />
        
                <Adaptive_Input
                  initial={'Value 1'}
                  focused={'Value 1'}
                  on_Input_Change={this.props.handle_value_1_input} />
        
                <Adaptive_Input
                  initial={'Value 2'}
                  focused={'Value 2'}
                  on_Input_Change={this.props.handle_value_2_input} />
              </form>
            );
          }
        }
        
        class Page extends React.Component {
          constructor(props) {
            super(props);
        
            this.state = {
              Name: 'No Name',
              Value_1: '0',
              Value_2: '0',
              Display_Value: '0'
            };
          }
        
          handle_text_input(new_text) {
            this.setState({
              Name: new_text
            });
          }
        
          handle_value_1_input(new_value) {
            new_value = parseInt(new_value);
            var updated_display = new_value + parseInt(this.state.Value_2);
            updated_display = updated_display.toString();
            this.setState({
              Value_1: new_value,
              Display_Value: updated_display
            });
          }
        
          handle_value_2_input(new_value) {
            new_value = parseInt(new_value);
            var updated_display = parseInt(this.state.Value_1) + new_value;
            updated_display = updated_display.toString();
            this.setState({
              Value_2: new_value,
              Display_Value: updated_display
            });
          }
        
          render() {
            return(
              <div>
                <h2>{this.state.Name}</h2>
                <h2>Value 1 + Value 2 = {this.state.Display_Value}</h2>
                <Form
                  handle_text_input={this.handle_text_input.bind(this)}
                  handle_value_1_input={this.handle_value_1_input.bind(this)}
                  handle_value_2_input={this.handle_value_2_input.bind(this)}
                />
              </div>
            );
          }
        }
        
        ReactDOM.render(<Page />, document.getElementById('app'));
        

        由于这个库,现在相同的代码被表单验证破解了:https://github.com/tkrotoff/react-form-with-constraints => http://jsfiddle.net/tkrotoff/k4qa4heg/

        const { FormWithConstraints, FieldFeedbacks, FieldFeedback } = ReactFormWithConstraints;
        
        class Adaptive_Input extends React.Component {
          static contextTypes = {
            form: PropTypes.object.isRequired
          };
        
          constructor(props) {
            super(props);
        
            this.state = {
              field: undefined
            };
        
            this.fieldWillValidate = this.fieldWillValidate.bind(this);
            this.fieldDidValidate = this.fieldDidValidate.bind(this);
          }
        
          componentWillMount() {
            this.context.form.addFieldWillValidateEventListener(this.fieldWillValidate);
            this.context.form.addFieldDidValidateEventListener(this.fieldDidValidate);
          }
        
          componentWillUnmount() {
            this.context.form.removeFieldWillValidateEventListener(this.fieldWillValidate);
            this.context.form.removeFieldDidValidateEventListener(this.fieldDidValidate);
          }
        
          fieldWillValidate(fieldName) {
            if (fieldName === this.props.name) this.setState({field: undefined});
          }
        
          fieldDidValidate(field) {
            if (field.name === this.props.name) this.setState({field});
          }
        
          handle_change(e) {
            var new_text = e.currentTarget.value;
            this.props.on_Input_Change(e, new_text);
          }
        
          render() {
            const { field } = this.state;
            let className = 'adaptive_placeholder_input_container';
            if (field !== undefined) {
              if (field.hasErrors()) className += ' error';
              if (field.hasWarnings()) className += ' warning';
            }
        
            return (
              <div className={className}>
                <input
                  type={this.props.type}
                  name={this.props.name}
                  className="adaptive_input"
                  required
                  onChange={this.handle_change.bind(this)} />
                <label
                  className="adaptive_placeholder"
                  alt={this.props.initial}
                  placeholder={this.props.focused} />
              </div>
            );
          }
        }
        
        class Form extends React.Component {
          constructor(props) {
            super(props);
        
            this.state = {
              Name: 'No Name',
              Value_1: '0',
              Value_2: '0',
              Display_Value: '0'
            };
          }
        
          handle_text_input(e, new_text) {
            this.form.validateFields(e.currentTarget);
        
            this.setState({
              Name: new_text
            });
          }
        
          handle_value_1_input(e, new_value) {
            this.form.validateFields(e.currentTarget);
        
            if (this.form.isValid()) {
              new_value = parseInt(new_value);
              var updated_display = new_value + parseInt(this.state.Value_2);
              updated_display = updated_display.toString();
              this.setState({
                Value_1: new_value,
                Display_Value: updated_display
              });
            }
            else {
              this.setState({
                Display_Value: 'Error'
              });
            }
          }
        
          handle_value_2_input(e, new_value) {
            this.form.validateFields(e.currentTarget);
        
            if (this.form.isValid()) {
              new_value = parseInt(new_value);
              var updated_display = parseInt(this.state.Value_1) + new_value;
              updated_display = updated_display.toString();
              this.setState({
                Value_2: new_value,
                Display_Value: updated_display
              });
            }
            else {
              this.setState({
                Display_Value: 'Error'
              });
            }
          }
        
          render() {
            return(
              <div>
                <h2>Name: {this.state.Name}</h2>
                <h2>Value 1 + Value 2 = {this.state.Display_Value}</h2>
        
                <FormWithConstraints ref={form => this.form = form} noValidate>
                  <Adaptive_Input
                    type="text"
                    name="name_input"
                    initial={'Name Input'}
                    focused={'Name Input'}
                    on_Input_Change={this.handle_text_input.bind(this)} />
                  <FieldFeedbacks for="name_input">
                    <FieldFeedback when="*" error />
                    <FieldFeedback when={value => !/^\w+$/.test(value)} warning>Should only contain alphanumeric characters</FieldFeedback>
                  </FieldFeedbacks>
        
                  <Adaptive_Input
                    type="number"
                    name="value_1_input"
                    initial={'Value 1'}
                    focused={'Value 1'}
                    on_Input_Change={this.handle_value_1_input.bind(this)} />
                  <FieldFeedbacks for="value_1_input">
                    <FieldFeedback when="*" />
                  </FieldFeedbacks>
        
                  <Adaptive_Input
                    type="number"
                    name="value_2_input"
                    initial={'Value 2'}
                    focused={'Value 2'}
                    on_Input_Change={this.handle_value_2_input.bind(this)} />
                  <FieldFeedbacks for="value_2_input">
                    <FieldFeedback when="*" />
                  </FieldFeedbacks>
                </FormWithConstraints>
              </div>
            );
          }
        }
        
        ReactDOM.render(<Form />, document.getElementById('app'));
        

        这里提出的解决方案是骇人听闻的,因为我试图让它接近原始的 jsfiddle。要使用 react-form-with-constraints 进行正确的表单验证,请查看https://github.com/tkrotoff/react-form-with-constraints#examples

        【讨论】:

          【解决方案5】:

          又一次解决同样的问题 - form-container on npm

          【讨论】:

            【解决方案6】:

            我最近花了一周时间研究了很多解决方案来验证我在应用中的表单。我从所有最受瞩目的人开始,但我找不到按预期工作的人。几天后,我变得非常沮丧,直到我发现了一个非常新的和惊人的插件: https://github.com/kettanaito/react-advanced-form

            开发人员反应迅速,经过我的研究,他的解决方案值得成为我认为最受关注的解决方案。我希望它可以帮助你,你会感激的。

            【讨论】:

              【解决方案7】:

              我写了This library,它允许您包装表单元素组件,并允许您以以下格式定义验证器:-

              <Validation group="myGroup1"
                  validators={[
                          {
                           validator: (val) => !validator.isEmpty(val),
                           errorMessage: "Cannot be left empty"
                          },...
                      }]}>
                          <TextField value={this.state.value}
                                     className={styles.inputStyles}
                                     onChange={
                                      (evt)=>{
                                        console.log("you have typed: ", evt.target.value);
                                      }
                                     }/>
              </Validation>
              

              【讨论】:

                【解决方案8】:

                您可以使用npm install --save redux-form

                我正在编写一个简单的电子邮件和提交按钮表单,用于验证电子邮件并提交表单。使用 redux-form,表单默认在 html onSubmit 操作上运行 event.preventDefault()。

                import React, {Component} from 'react';
                import {reduxForm} from 'redux-form';
                
                class LoginForm extends Component {
                  onSubmit(props) {
                    //do your submit stuff
                  }
                
                
                  render() {
                    const {fields: {email}, handleSubmit} = this.props;
                
                    return (
                      <form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
                        <input type="text" placeholder="Email"
                               className={`form-control ${email.touched && email.invalid ? 'has-error' : '' }`}
                          {...email}
                        />
                          <span className="text-help">
                            {email.touched ? email.error : ''}
                          </span>
                        <input type="submit"/>
                      </form>
                    );
                  }
                }
                
                function validation(values) {
                  const errors = {};
                  const emailPattern = /(.+)@(.+){2,}\.(.+){2,}/;
                  if (!emailPattern.test(values.email)) {
                    errors.email = 'Enter a valid email';
                  }
                
                  return errors;
                }
                
                LoginForm = reduxForm({
                  form: 'LoginForm',
                  fields: ['email'],
                  validate: validation
                }, null, null)(LoginForm);
                
                export default LoginForm;
                

                【讨论】:

                • 那又怎样?人们仍然会提出这个问题,如果有解决问题的新方法,为什么不呢? Redux 与否 - 它完全有效。
                【解决方案9】:

                首先,我将在下面提到一个示例:http://jsbin.com/rixido/2/edit

                如何使用 React.JS 正确验证输入值?

                随心所欲。 React 用于渲染数据模型。数据模型应该知道什么是有效的。您可以使用 Backbone 模型、JSON 数据或任何您想要表示数据及其错误状态的东西。

                更具体地说:

                React 通常与您的数据无关。它用于渲染和处理事件。

                遵循的规则是:

                1. 元素可以改变它们的状态。
                2. 他们不能改变道具。
                3. 他们可以调用回调来更改顶级道具。

                如何决定某个东西应该是道具还是状态?考虑一下:除了文本字段之外,您的应用程序的任何部分都想知道输入的值是错误的吗?如果不是,则使其成为一个状态。如果是,它应该是一个道具。

                例如,如果您想要一个单独的视图来呈现“您在此页面上有 2 个错误”。那么顶级数据模型必须知道您的错误。

                该错误应该出现在哪里?
                如果您的应用程序正在渲染 Backbone 模型(例如),则模型本身将具有您可以使用的 validate() 方法和 validateError 属性。您可以渲染其他可以执行相同操作的智能对象。 React 还说尽量减少 props 并生成其余数据。因此,如果您有一个验证器(例如 https://github.com/flatiron/revalidator),那么您的验证可能会逐渐减少,任何组件都可以使用匹配的验证检查 props 以查看它是否有效。

                这在很大程度上取决于您。

                (我个人使用 Backbone 模型并在 React 中渲染它们。我有一个顶级错误警报,我会在任何地方显示错误,并描述错误。)

                【讨论】:

                • 'React 还说尽量减少 props' - 你知道 React 在哪里记录这个吗?
                • “找出应用程序状态的绝对最小表示需要是什么,并按需计算您需要的所有其他内容。” facebook.github.io/react/docs/thinking-in-react.html 第三步
                • 不应该是“React 也说尽量将状态保持在最低限度”吗?
                • @MosesLee 使用道具而不是组件状态将每个组件的状态保持在最低限度。在渲染调用中计算派生值而不是存储它们,将道具(和状态)保持在最低限度。我们试图在两种意义上都将 state 保持在最低限度,在第二种意义上将 props 保持在最低限度。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2023-02-04
                • 2013-12-03
                • 2023-03-31
                • 2021-10-20
                • 2012-01-13
                • 2020-02-13
                • 2016-01-29
                相关资源
                最近更新 更多