Reputation: 551
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
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
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