4

I was trying to find a good example of form validation in react. I found validation by sending events from parent form to its input children, and calling validate method on each.

I've also found a method by looping through form children and calling setState on a child after field is validated.

As far as I know these are anti-patterns and the react way is to call it through props callbacks - react.js custom events for communicating with parent nodes

Let say I have a component:

class Main extends React.Component {
 ...
 onSubmitHandler() {
  ...
 }

 render() {
  return (
   <FormComponent onSubmit={this.onSubmitHandler.bind(this)}>
     <InputComponent 
       value="foo"
       validation="required"
     />
     <input type="submit" value="Save" />
   </Form>  
  );
 }
}

class FormComponent extends React.Component {
  onSubmit() {
   // TODO: validation
  }

  render() {
   return (
     <form
       onSubmit={this.onSubmit.bind(this)}
     >
       {this.props.children}
     </form>
   );
  }
}

class InputComponent extends React.Component {
  constructor(props) {
   super(props);
   this.state = {
     value: props.value
   }
  }

  render() {
    return (
      <input 
        type="text"
        value={value}
    );
  }
}

I can't really work it out, how should I do the validation of each input via callbacks passing through props.

Community
  • 1
  • 1
DeBoer
  • 551
  • 1
  • 7
  • 16
  • This is what I've got at the moment in ES5 https://jsfiddle.net/69z2wepo/13034/ I really hope there is a better way of doing this. – DeBoer Jul 29 '15 at 19:54

2 Answers2

1

I have added some code to yours. In summary what I have done is adding a ref prop in your inputComponent to expose its validate function (which i have added in the inputComponent). The validate function gets called when the submit listener is triggered. Eventually the validate function calls the "isValid" callback which we have passed over the props. I think this is a way you could do it.

class Main extends React.Component {
 ...
 onSubmitHandler() {
  ...
  this.refs.input1.validate();
 }

 render() {
  return (
   <FormComponent onSubmit={this.onSubmitHandler.bind(this)}>
     <InputComponent 
       ref="input1"
       value="foo"
       validation="required"
       isValid={this.isValidInput}
     />
     <input type="submit" value="Save" />
   </Form>  
  );
 }
 isValidInput(someParam) {
   console.log(someParam);
 }
}

class FormComponent extends React.Component {
  onSubmit() {
   // TODO: validation
  }

  render() {
   return (
     <form
       onSubmit={this.onSubmit.bind(this)}
     >
       {this.props.children}
     </form>
   );
  }
}

class InputComponent extends React.Component {
  constructor(props) {
   super(props);
   this.state = {
     value: props.value
   }
  }

  render() {
    return (
      <input 
        type="text"
        value={value}
    );
  }
  validate(){
    this.props.isValid(YesOrNo);
  }
}

I hope this helps

Max Bumaye
  • 1,017
  • 10
  • 17
  • This works fine, thank you. However I was wondering if the whole validation can happen on `FormComponent` instead of `Main`, so I could use this `FormComponent` as generic one. – DeBoer Jul 29 '15 at 13:22
  • never done this but in formComponents "componentWillMount" you could possibly itterate over the children like this this.props.children.forEach(function(child){child.ref="randomstring";});. then do something like for(var ref in this.refs){ref.validate()} – Max Bumaye Jul 29 '15 at 13:31
1

Here's a simplified example (Child to Parent):

var Bar = React.createClass({
  validate : function () {
    var number = React.findDOMNode(this.refs.input).value;
    this.props.check(number, this.success, this.fail);
  },
  success : function () {
    alert('Success');
  },
  fail : function () {
    alert('Fail');
  },
  render : function () {
    return (
      <div>
        <input type='number' min='1' max='20' ref='input'/>
        <br/>
        <button onClick={this.validate}>Click me to validate</button>
        <br/>
        (Values 1 - 10 are valid, anything else is invalid)
      </div>
    );
  }
});

var Foo = React.createClass({
  check : function (number, success, fail) {
    if (number >= 1 && number <= 10) {
      success();
    } else {
      fail();
    }
  },
  render : function () {
    return (
      <div>
        <Bar check={this.check} />
      </div>
    );
  }

});

In this example, <Bar/> is the child, and <Foo/> is the parent. <Bar/> is responsible for handling user input, and when the button is clicked, it calls a function from <Foo/> to perform the validation, which, when finished, will call one of the two functions in <Bar/> depending on the results.

Here's what it looks like: http://jsbin.com/feqohaxoxa/edit?js,output

-- EDIT --

Here's an example for Parent to Child:

var Parent = React.createClass({
  getInitialState : function () {
    return({validate : false});
  },
  click : function () {
    this.setState({validate : true});
  },
  done : function () {
    this.setState({validate : false});
  },
  render : function () {
    return (
      <div>
        <button onClick={this.click}>Validate children</button>
        <br/>
        <Child num={1} validate={this.state.validate} done={this.done}/>
        <Child num={2} validate={this.state.validate} done={this.done}/>
        <Child num={3} validate={this.state.validate} done={this.done}/>
      </div>
    );
  }
});

var Child = React.createClass({
  componentWillReceiveProps : function (nextProps) {
    if (nextProps.validate == true && this.props.validate == false) {
      var number = React.findDOMNode(this.refs.input).value;
      if (number >= 1 && number <= 10) {
        alert("Child " + this.props.num + " valid");
      } else {
        alert("Child " + this.props.num + " invalid");
      }
      this.props.done();
    }
  },
  render : function () {
    return (
      <div>
        <input type="number" min="1" max="20" ref='input'/>
      </div>
    );
  }
});

For this one, I use a state in the parent to indicate if I want to check validation. By changing the state from false to true, I'm forcing a re-render, which passes true down to the children. When the children receive the new prop, they check if its true and if its different than the last one received, and perform validation if so. After validating, they use a callback to tell the parent to set its validate state back to false.

Demo: http://output.jsbin.com/mimaya

Michael Parker
  • 12,724
  • 5
  • 37
  • 58
  • Thanks. But you are triggering validation from the child and I'd like from the parent. – DeBoer Jul 29 '15 at 14:41
  • I guess I misunderstood what you're trying to do. I thought that you wanted some user input on a child component, and validation to occur on the parent. Is this not correct? – Michael Parker Jul 29 '15 at 14:45
  • I'd like to have a button on form which trigger onSubmit which then validate the children. – DeBoer Jul 29 '15 at 14:49
  • Updated my answer for you. – Michael Parker Jul 29 '15 at 15:17
  • This is all great, but in the simple example when I have children specified like you implemented. The problem starts when I just put `this.props.children` like in my example. I was trying to use `React.cloneElement` on my children, but I can't find a way of communicating that way. Try to extend your example so you are going to use this.props.children on parent component. – DeBoer Jul 29 '15 at 15:40
  • Seems like all you'd need to do is pass in the relevant props to your `` children and it would work just fine. I can re-write the example for you when I have time but I think you should just try that and see if you can get it working on your own. – Michael Parker Jul 29 '15 at 16:35
  • OK, this is what I have. Please tell me there is a better way of doing it. For now I'm marking fields invalid on submit. https://jsfiddle.net/69z2wepo/13034/ – DeBoer Jul 29 '15 at 19:53