DeBoer
DeBoer

Reputation: 551

React form validation - callbacks not events

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.

Upvotes: 4

Views: 4532

Answers (2)

Michael Parker
Michael Parker

Reputation: 12966

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

Upvotes: 1

Max Bumaye
Max Bumaye

Reputation: 1009

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

Upvotes: 1

Related Questions