Reputation: 1897
I am not so familiar with react, I try to build the following abstraction over sign in / forms etc.
Take a look at this:
var SignUpForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
console.log(this.refs.iitu_id.getDOMNode().value.trim())
// iitu_id = this.refs.iitu_id.getDOMNode().value.trim();
// password = this.refs.password.getDOMNode().value.trim();
var error = UserValidator.valid({iitu_id: iitu_id, password: password});
if (error) {
this.setState({"errors": error });
// console.log(error);
} else {
// console.log(error);
}
},
getInitialState: function() {
return {
'errors': {
iitu_id: null,
password: null
}
};
},
render: function() {
return (
/*jshint ignore:start */
<form className="form-horizontal" onSubmit={this.handleSubmit} >
<FormGroup label="iitu id" error_msg={this.state.errors.iitu_id} fieldName="iitu_id" fieldType="text" />
<FormGroup label="password" error_msg={this.state.errors.password} fieldName="password" fieldType="password" />
<ButtonGroup text="Войти"/>
</form>
/*jshint ignore:end */
);
}
});
var FormGroup = React.createClass({
render: function() {
var formGroupCss = 'form-group';
if (this.props.error_msg){
formGroupCss = 'form-group has-error';
}
return (
/*jshint ignore:start */
<div className={formGroupCss}>
<Label fieldName={this.props.fieldName}>{this.props.label}</Label>
<InputField type={this.props.fieldType}>{this.props.label}</InputField>
<label className="control-label" for="inputError1">{this.props.error_msg}</label>
</div>
/*jshint ignore:end */
);
}
});
var ButtonGroup = React.createClass({
render: function () {
return (
/*jshint ignore:start */
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button className="btn btn-default">{this.props.text}</button>
</div>
</div>
/*jshint ignore:end */
);
}
});
var InputField = React.createClass({
render: function() {
return (
/*jshint ignore:start */
<div className="col-sm-5">
<input className="form-control" type={this.props.fieldType} placeholder={this.props.children}/>
</div>
/*jshint ignore:end */
);
}
});
exports.SignUpForm = SignUpForm;
there is a bit too much code, but I think it is pretty easy to read. The question is how can I get the value of my InputField class when I press the submit button (simply get form value)? There is problem that my input tag deep inside DOM. Additional question is it good to have the following code design I mean describe each logical component as new 'class' ?
Upvotes: 1
Views: 6661
Reputation: 62793
If you give your form inputs name
attributes, you can use the form's .elements
collection to access its inputs.
I recently split the code newforms uses for this out into a reusable get-form-data module, which would let you do this assuming your inputs have name
attributes:
var getFormData = require('get-form-data'); // Or just use the browser bundle
var SignUpForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
var data = getFormData(e.target, {trim: true});
var error = UserValidator.valid(data);
if (error) {
this.setState({errors: error});
} else {
// ...
}
},
// ...
Or, if you need to get input as it's given, you can add an onChange
handler to the <form>
or some other component which contains all the form inputs, instead of having to do each one individually:
handleChange: function(e) {
var form = e.target.form;
var name = e.target.name;
var data = getFormData.getNamedFormElementData(form, name, {trim: true});
// ...
},
render: function() {
return <form onSubmit={this.handleSubmit} onChange={this.handleChange}>
{/* ... */}
</form>
}
});
Upvotes: 7
Reputation: 1600
Concerning your second question - it is a bad approach to add a component for every logical element. In your example - all of the components, (besides the first) has no logic, only 'render'. this design adds many useless lines of code and "this.props" repetitions. In your example, I would have just add everything to the render of the first component.
Now, let say you have two components, and want to reach the child from the parent:
var SignUpForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
//now - reach your inner comp with refs
console.log(this.refs.inner.refs.username.getDOMNode().value.trim());
},
render: function() {
return (
//and pass the submit function as a callback
<InnerForm ref="inner" submit={this.props.handleSubmit}/>
);
}
});
var InnerForm = React.createClass({
render: function() {
return (
<form>
<input type="text" ref="username"/>
<input type="submit" onClick={this.props.submit} >
</form>
);
}
});
In your example there was to many components inside components to do what I did. Still, there are cases when you need long linage of child components. Using this.refs.a.refs.b.refs.c etc - might be pretty ugly, and also prevents re-usability.
in those cases try using any MVC architecture (I'm using reflux and loving it)
Upvotes: 1
Reputation: 283
In the absence of a framework like Angular or Flux, shoot data between your components using callbacks.
The technique used here is explained in detail on React's website. Another solution, however, is to use a form library. I took a similar route and tried to build form components from scratch -- it works, but it's a path others have already cleared. Check out newforms and react-forms.
Note that the code I shared is untested, though it should be very close to working.
var SignUpForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
console.log(this.iitu_id + ' and ' + this.password + ' should work.');
var error = UserValidator.valid({iitu_id: iitu_id, password: password});
if (error) {
this.setState({"errors": error });
// console.log(error);
} else {
// console.log(error);
}
},
getInitialState: function() {
return {
'errors': {
iitu_id: null,
password: null
}
};
},
updateForm: function(field, value) {
/* Or do something like this using underscore.js:
var form = _.clone(this.form);
form[field] = value;
this.setState({ form: form });
This approach let's you grab the form object on
submit instead of hunting for the form values
in SignUpForm's state.
*/
this.setState({ field: value });
},
render: function() {
return (
/*jshint ignore:start */
<form className="form-horizontal" onSubmit={this.handleSubmit} >
<FormGroup update={this.updateForm} label="iitu id" error_msg={this.state.errors.iitu_id} fieldName="iitu_id" fieldType="text" />
<FormGroup label="password" error_msg={this.state.errors.password} fieldName="password" fieldType="password" />
<ButtonGroup text="Войти"/>
</form>
/*jshint ignore:end */
);
}
});
var FormGroup = React.createClass({
handleChange: function(event) {
this.props.update(this.props.fieldName, event.target.value);
},
render: function() {
var formGroupCss = 'form-group';
if (this.props.error_msg){
formGroupCss = 'form-group has-error';
}
return (
/*jshint ignore:start */
<div className={formGroupCss}>
<Label fieldName={this.props.fieldName}>{this.props.label}</Label>
<InputField handleChange={this.handleChange} type={this.props.fieldType}>{this.props.label}</InputField>
<label className="control-label" for="inputError1">{this.props.error_msg}</label>
</div>
/*jshint ignore:end */
);
}
});
var ButtonGroup = React.createClass({
render: function () {
return (
/*jshint ignore:start */
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button className="btn btn-default">{this.props.text}</button>
</div>
</div>
/*jshint ignore:end */
);
}
});
var InputField = React.createClass({
render: function() {
return (
/*jshint ignore:start */
<div className="col-sm-5">
<input onChange={this.props.handleChange} className="form-control" type={this.props.fieldType} placeholder={this.props.children}/>
</div>
/*jshint ignore:end */
);
}
});
exports.SignUpForm = SignUpForm;
Specifically, take note of the callback functions being passed from parent to child.
updateForm
to FormGrouphandleChange
to InputFieldThen to get the data back to the top, you just reverse the process.
<input/>
changes, it runs handleChange
handleChange
runs, it passes the FormGroup's fieldName
and value
to SignUpFormUpvotes: 2
Reputation: 1319
I would suggest an onChange
event listener to your inputs that use callbacks to relay the information all the way back up to your top level component.
var SignUpForm = React.createClass({
getInitialState: function() {
return {
inputs: {
id: '',
password: ''
}
};
},
render: function() {
return (
<form className="form-horizontal" onSubmit={this.handleSubmit} >
<FormGroup ... callback={this.handleIdChange} />
<FormGroup ... callback={this.handlePasswordChange} />
<ButtonGroup text="Войти"/>
</form>
);
},
handleIdChange: function(newId) {
this.setState({
inputs: {
id: newId.target.value,
password: this.state.inputs.password
}
});
},
handlePasswordChange: function(newPassword) { ... }
});
then pass that callback down to the base level form fields and use them for handling the onChange
event.
var InputField = React.createClass({
render: function() {
return (
/*jshint ignore:start */
<div className="col-sm-5">
<input ... onChange={this.props.callback}/>
</div>
/*jshint ignore:end */
);
}
});
then with your handleSubmit
just output the values in the this.state.inputs
object.
Upvotes: 1