【问题标题】:ReactJS form validation when state is not immediately updated状态未立即更新时的 ReactJS 表单验证
【发布时间】:2015-03-23 05:52:33
【问题描述】:

我正在尝试在我的注册表单上使用 ReactJS 创建客户端验证。我正在使用 http://validatejs.org/ 库进行验证,并使用 https://github.com/jhudson8/react-semantic-ui 组件来呈现语义 UI React 组件。这是代码。

var constraints = {
  email: {
    presence: true, 
    email:true
  }, 
  password: {
    presence: true,
    length: { minimum: 5 }
  }
}

var RegistrationForm = React.createClass({

  getInitialState: function() {
    return { 
      data: {},
      errors: {}
    };
  },

  changeState: function () {
    this.setState({
      data: {
        email: this.refs.email.getDOMNode().value,
        password: this.refs.password.getDOMNode().value,
        password_confirmation:  this.refs.password_confirmation.getDOMNode().value
      }
    });
    console.log("State after update");
    console.log(this.state.data);
  },

  handleChange: function(e) {
    this.changeState();
    var validation_errors = validate(this.state.data, constraints);

    if(validation_errors){
      console.log(validation_errors);
      this.setState({errors: validation_errors});
    }
    else
      this.setState({errors: {}});
  },

  handleSubmit: function(e) {
    e.preventDefault();
    //code left out..
  },

  render: function() {
    var Control = rsui.form.Control;
    var Form = rsui.form.Form;
    var Text = rsui.input.Text;
    var Button = rsui.form.Button;
    return (
      <Form onSubmit={this.handleSubmit} onChange={this.handleChange}>
        <h4 className="ui dividing header">New User Registration</h4>
        <Control label="Email" error={this.state.errors.email}>
          <Text name="email" type="email" ref="email"  key="email" value={this.state.data.email}></Text>
        </Control>
        <Control label="Password" error={this.state.errors.password}>
          <Text name="password" type="password" ref="password" key="password" value={this.state.data.password}></Text>
        </Control>
        <Control label="Password Confirmation">
          <Text name="password_confirmation" type="password" ref="password_confirmation" key="password_confirmation" value={this.state.data.password_confirmation}></Text>
        </Control>
        <Button> Register </Button>
      </Form>);
  }
});

我遇到的问题是,当我调用 this.setState 时,状态不会立即更新,所以当我调用 validate(this.state.data, constraints) 时,我正在验证以前的状态,所以用户的 UI 体验变得很奇怪,例如:

如果我的电子邮件字段中有“example@em”并输入“a”,它将验证字符串“example@em”而不是“example@ema”,因此本质上它总是验证新密钥之前的状态中风。我必须在这里做一些根本错误的事情。我知道组件的状态不会立即更新,只有在渲染完成后。

我应该在渲染函数中进行验证吗?

--- 解决方案---

像 Felix Kling 建议的那样向 setState 添加回调解决了这个问题。这是带有解决方案的更新代码:

var RegistrationForm = React.createClass({

  getInitialState: function() {
    return { 
      data: {},
      errors: {}
    };
  },

  changeState: function () {
    this.setState({
      data: {
        email: this.refs.email.getDOMNode().value,
        password: this.refs.password.getDOMNode().value,
        password_confirmation: this.refs.password_confirmation.getDOMNode().value
      }
    },this.validate);
  },

  validate: function () {
    console.log(this.state.data);
    var validation_errors = validate(this.state.data, constraints);

    if(validation_errors){
      console.log(validation_errors);
      this.setState({errors: validation_errors});
    }
    else
      this.setState({errors: {}});
  },

  handleChange: function(e) {
    console.log('handle change fired');
    this.changeState();
  },

  handleSubmit: function(e) {
    e.preventDefault();
    console.log(this.state);
  },

  render: function() {
    var Control = rsui.form.Control;
    var Form = rsui.form.Form;
    var Text = rsui.input.Text;
    var Button = rsui.form.Button;
    return (
      <Form onSubmit={this.handleSubmit} onChange={this.handleChange}>
        <h4 className="ui dividing header">New Rowing Club Registration</h4>
        <Control label="Email" error={this.state.errors.email}>
          <Text name="email" type="email" ref="email"  key="email" value={this.state.data.email}></Text>
        </Control>
        <Control label="Password" error={this.state.errors.password}>
          <Text name="password" type="password" ref="password" key="password" value={this.state.data.password}></Text>
        </Control>
        <Control label="Password Confirmation">
          <Text name="password_confirmation" type="password" ref="password_confirmation" key="password_confirmation" value={this.state.data.password_confirmation}></Text>
        </Control>
        <Button> Register </Button>
      </Form>);
  }
});

--- 更好的解决方案 -----

请参阅下面的 FakeRainBrigand 解决方案。

【问题讨论】:

    标签: javascript forms validation reactjs react-rails


    【解决方案1】:

    当您想从状态中获取数据时,最简单的方法就是在您真正需要它之前。在这种情况下,您只需要在渲染中使用它。

    validate: function (data) {
      var validation_errors = validate(data, constraints);
    
      if(validation_errors){
        return validation_errors;
      }
    
      return {};
    },
    
    render: function() {
        var errors = this.validate(this.state.data);
        ...
          <Control label="Email" error={errors.email}>
            ...
    

    State 应该很少用作派生数据缓存。如果您确实想在设置状态时导出数据,请非常小心,并将其设为实例属性(例如 this.errors)。

    因为 setState 回调实际上会导致额外的渲染周期,所以您可以改为不可变地更新数据,并将其传递给 this.validate(请参阅上面代码中我如何使 validate 不依赖于 this.state.data 的当前值?)。

    根据您当前的 changeState,它看起来像这样:

    changeState: function () {
      var update = React.addons.update;
      var getValue = function(ref){ return this.refs[ref].getDOMNode().value }.bind(this);
    
      var data = update(this.state.data, {
          email: {$set: getValue('email')},
          password: {$set: getValue('password')},
          password_confirmation: {$set: getValue('password_confirmation')}
       });
    
       this.errors = this.validate(data);
       this.setState({data: data});
     },
    
     // we'll implement this because now it's essentially free 
     shouldComponentUpdate: function(nextProps, nextState){
       return this.state.data !== nextState.data;
     }
    

    在 cmets/answers 中,人们说错误应该处于状态,这有时是正确的。当您无法在错误处于状态的情况下实现渲染时,它们应该处于状态。当您可以通过从状态派生现有数据来实现时,这意味着将其置于状态将是多余的。

    冗余的问题在于它增加了很难追踪错误的可能性。您无法避免将数据保持为状态的一个示例是使用异步验证。没有冗余,因为您不能仅从表单输入中得出它。

    我也犯了一个错误,没有更新错误状态。 – blushrt

    这正是原因。

    【讨论】:

    • 谢谢!此解决方案的渲染要少得多。我不知道 React 插件。我必须做的一件事是将 .bind(this) 添加到 getValue 函数,因此 this.refs 调用将具有正确的 this 上下文。
    • 对于使用 react-rails gem 的任何人,请务必将 config.react.addons = true 添加到 application.rb 以便您可以访问 React.addons
    • 但我不同意这里的表单错误是派生的数据缓存,它们也应该是状态的一部分。
    • @CoryDanielson,见this page in the docs
    【解决方案2】:

    为什么不在设置状态之前验证数据?错误也是状态,因此将它们设置为与状态的其余部分相同的方式是合乎逻辑的。

    changeState: function () {
        var data = {
                email: this.refs.email.getDOMNode().value,
                password: this.refs.password.getDOMNode().value,
                password_confirmation: this.refs.password_confirmation.getDOMNode().value
            },
            errors = validate(data, constraints) || {};
    
        this.setState({
            data: data,
            errors: errors
        });
    },
    

    【讨论】:

    • 我也认为错误应该是表单状态的一部分。
    【解决方案3】:

    一个简单的解决方案是改为在changeState 方法中检查数据的有效性。

    另一种解决方案是传递callback to setState:

    此外,您可以提供一个可选的回调函数,该函数在setState 完成并重新渲染组件后执行。

    [...]

    setState() 不会立即改变this.state,而是创建一个挂起的状态转换。调用此方法后访问 this.state 可能会返回现有值。

    无法保证对setState 的调用同步操作,并且可能会批处理调用以提高性能。

    【讨论】:

    • 检查 changeState 中数据的有效性不起作用,但向 setState 添加回调起作用。谢谢!我知道这可能不是最佳解决方案,我发现反应中的验证可能非常复杂,我发现了这个很棒的资源christianalfoni.github.io/javascript/2014/10/22/…,这可能是一个更好的解决方案。
    • @blushrt 为什么没有在 changeState 工作期间检查有效性?
    • @CoryDanielson,我也犯了一个错误,没有更新错误状态。所以它现在应该可以工作了。
    猜你喜欢
    • 2018-07-25
    • 1970-01-01
    • 1970-01-01
    • 2020-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-25
    • 2016-11-16
    相关资源
    最近更新 更多