jurassix / react-validation-mixin

Simple validation mixin (HoC) for React.
MIT License
283 stars 38 forks source link

How to validate parent form component while field validation happens in child components #66

Open idealistic opened 8 years ago

idealistic commented 8 years ago

@jurassix, What if every form field is its own react component/class. The joi validation happens inside each component. The parent component, i.e. the form, just adds each component. In this case, how do you check if the form is valid?

I tried calling this.validate from the form component but it is throwing undefined.

jurassix commented 8 years ago

I'll try to sketch an example of a way to do this. Basically you need the parent to control the children; make your children pure (defined fully by their props.)

jurassix commented 8 years ago

In this way the state is controlled at the parent and the validation methods are passed to the children but bound to the parents. I'll put an example together tomorrow.

idealistic commented 8 years ago

I need the state to be controlled at the children level. The reason is that the children can be re-used with any parent form and need to be self sufficient. The validation logic for the children need to exist in the children to avoid dealing with recreating the validation and state for the children every time they are reused with different parents.

jurassix commented 8 years ago

Ok, in that case we have the issue of the parent having a getValidatorData method but the children having the data. This can be solved a few ways. Basically, you need a way for the parent to access or passed the data. Since you said the data is managed by the child then you could expose a method like getValue on the child that you can call from the parent.

A great reference to this solution can be found in the <Input /> component from react-bootstrap: https://github.com/react-bootstrap/react-bootstrap/blob/master/src/InputBase.js#L12

jurassix commented 8 years ago

And the spec for the Input component can be found here: https://github.com/react-bootstrap/react-bootstrap/blob/master/test/InputSpec.js#L56

jurassix commented 8 years ago

Well...now I'm thinking there may be a caveat here. Still looking for a better example.

jurassix commented 8 years ago
  componentDidMount: function() {
    this.refs.input.getValue(); //yolo
  },

  render: function() {
    return (
      <form >
        <Input ref="input" type="input" defaultValue="yolo"/>
      </form>
    );
idealistic commented 8 years ago

That will only get the value, how can I force form validation onSubmit of the form from the parent form component while the validation logic is in the children (form input fields)?

jurassix commented 8 years ago

In this example you pass the handleValidation down to the children and expose the state to the parent. Now you can call this.props.isValid() before submit.

 getValidatorData: function() {
    return {
      name: this.refs.name.getValue(),
    };
  },
  render: function() {
    return (
      <form >
        <Input ref="name" type="input" defaultValue="yolo" handleValidation={this.props.handleValidation('name')} />
      </form>
    );
  }
idealistic commented 8 years ago

Looks like I can call validate on each component from the parent via refs and it works.

Because each validate is running asynchronously, I need to know when it is done. Thoughts?

var validForm = false;
children.map(
            function (child) {
                child.validate(function(error) {
                    validForm = false;
                    if(!error) {
                        validForm = true;
                    }
                });
            }
        )
jurassix commented 8 years ago

Maybe provide a simple parent child example so I can see exactly what your doing and advise more specifically.

But, if you need control flow on async actions, just wrap them in a promise and wait for them all to resolve. Bluebird has some simple ways to handle this scenario.

rgalite commented 8 years ago

I'm interested in the answer too. Here is an example:

I have a form:

class Form extends Component {
  constructor(props) {
    super(props);

    this.validatorTypes = {
      name: Joi.string().alphanum().required().label('Name')
    };

    this.state = {
      name: this.props.settings.name
    };
  }

  getValidatorData() {
    return this.state;
  }

  render {
    return (
      <form>
         <input type="text" onBlur={this.handleValidation('name')} /><br />
         <AuthenticationInput />
      </form>
    );
  }
}

And another class:

class AuthenticationInput extends Component {
  constructor(props) {
    super(props);

    this.state = {
      username: '',
      password: '',
    };

    this.validatorTypes = {
      username: Joi.string().required(),
      password: Joi.string().required()
    };
  }

  getValidatorData() {
    return this.state;
  }

  render() {
    <input type="text" onBlur={this.handleValidation('username') /><br />
    <input type="text" onBlur={this.handleValidation('password') />
  }
}

AuthenticationInput has its own logic to validate its inputs, it works and it's fine. Form does not know how AuthenticationInput validation logic works, and it's fine.

But if I submit Form, I want AuthenticationInput validation logic to be triggered automatically, as it is an element of Form.

I could reference the AuthenticationInput with the ref attribute and trigger its validation from the Form submit handler as @idealistic suggested but it does not feel natural.

nclong87 commented 6 years ago

@RudthMael Your idea is good, but it doesn't validate if I don't put anything. Do you have any solution to fix it without using refs in parent?