I have a react based form with more than 10 fields in it, I have states for those ten form
fields (controlled component) .
Most of these input
fields are of type text
only but other types fields will be added later.
Problem is that I need to write 10 change handlers for them to set state correctly and then add method bindings for each of them in constructor .
I am quite new to react and may be not aware about correct methodologies and techniques.
Please guide me to how to improve my current code structure and avoid writing boiler plates and repetitive error prone code.
My current Registration component is like below -
export default class Register extends Component {
constructor(props){
super(props);
this.state = {
regName : '',
regAdd1 : '',
regAdd2 : '',
regState : '',
regZipCode : '',
regCity : '',
regPhone : ''
};
// add bindings .... ugh..
this.changeRegAdd1 = this.changeRegAdd1.bind(this);
this.changeRegAdd2 = this.changeRegAdd2.bind(this);
//Similary binding for other handlers...
}
// add individual field change handlers ... ugh...
changeRegName(e) {
this.setState({regName:e.target.value});
}
changeRegAdd1(e) {
this.setState({regAdd1:e.target.value});
}
changeRegAdd2(e) {
this.setState({regAdd2:e.target.value});
}
changeRegState(e) {
this.setState({regState:e.target.value});
}
// Similary for other change handler ....
handleSubmit(e) {
e.preventDefault();
// validate then do other stuff
}
render(){
let registrationComp = (
<div className="row">
<div className="col-md-12">
<h3>Registration Form</h3>
<fieldset>
<div className="form-group">
<div className="col-xs-12">
<label htmlFor="regName">Name</label>
<input type="text" placeholder="Name"
onChange={this.changeregName} value = {this.state.regName} className="form-control" required autofocus/>
</div>
</div>
<div className="form-group">
<div className="col-xs-12">
<label htmlFor="regAdd1">Address Line1</label>
<input
type = "text"
placeholder = "Address Line1"
onChange = {this.changeregAdd1}
value = {this.state.regAdd1}
className = "form-control"
required
autofocus
/>
<input
type = "text"
placeholder = "Address Line2"
onChange = {this.changeregAdd2}
value = {this.state.regAdd2}
className = "form-control"
required
autofocus
/>
</div>
</div>
<div className="form-group">
<div className="col-xs-6">
<label htmlFor="regState">State</label>
<input
type = "text"
placeholder = "State"
onChange = {this.changeregState}
value = {this.state.regState}
className = "form-control"
required
autofocus
/>
</div>
<div className="col-xs-6">
<label htmlFor="regZipCode">Zip Code</label>
<input
type = "text"
placeholder = "Zip Code"
onChange = {this.changeregZipCode}
value = {this.state.regZipCode}
className = "form-control"
required
autofocus
/>
</div>
</div>
<div className="form-group">
<div className="col-xs-12">
<label htmlFor="regCity">City</label>
<input
type = "text"
placeholder = "City"
title = "City"
onChange = {this.changeregCity}
value = {this.state.regCity}
className = "form-control"
required
autofocus
/>
</div>
</div>
{/* other form fields */}
</fieldset>
</div>
</div>
);
return registrationComp;
}
}
There can be other lots of ways to do it which I may not be aware.
But I prefer to do change handling in a common method for certain type of common fields such as <input type of text />
This is a sample input field -
<input
onChange = {this.onChange}
value = {this.state.firstName}
type = "text"
name = {"firstName"}
/>
I keep both state's field name and input's "name" attribute same. After that I write a common change handler for all such fields.
Need to write just one line in change handler.
onChange(e) {
this.setState({[e.target.name]: e.target.value});
}
I am using es6's dynamic property setting from string as property name
{ ["propName] : propValue }.
To avoid writing manual binding for methods in react component you can follow below two approaches -
create a method like this in your component.
_bind(...methods) {
methods.forEach( (method) => this[method] = this[method].bind(this) );
}
use _bind
to bind your methods.
constructor(props){
super(props);
this.state = {
regName : '',
regAdd1 : '',
regAdd2 : '',
regState : '',
regZipCode : '',
regCity : '',
regPhone : ''
};
// add bindings .... ugh..
//this.changeRegAdd1 = this.changeRegAdd1.bind(this);
//this.changeRegAdd2 = this.changeRegAdd2.bind(this);
//Similary binding for other handlers...
this._bind(
'changeRegName',
'changeReg1' , 'changeRegAdd2'
// and so on.
);
}
EDIT :
Adding validation -
set a state to indicate that form has errors. Specific error details can be found in state's error object.
validateInput() { let errors = {}; Object.keys(this.state) .forEach((stateKey) => { isEmpty(this.state[stateKey]) ? (errors[stateKey] = `*required` ) : null; }); return { errors, isValid : isEmptyObj(errors) }; } isFormValid() { const { errors, isValid } = this.validateInput(); if (!isValid) { this.setState({ errors}); } return isValid; } onSubmit(e) { e.preventDefault(); this.setState({errors : {}}); if (this.isFormValid()) { // Perform form submission } }
I have used to utility method calld isEmpty
, isEmptyObj
. They just check if object is null or undefined or field is empty.
I hope this helps.
You could create a higher order component to handle a lot of that for you. Redux Form has a pretty good pattern you could model it after if you wanted.
You would essentially end up with something like the following (I have not tested this at all, but it should work pretty well):
export class Field extends Component {
handleChange = (event) => this.props.onChange(this.props.name, event.target.value)
render() {
const InputComponent = this.props.component
const value = this.props.value || ''
return (
<InputComponent
{...this.props}
onChange={this.handleChange}
value={value}
/>
}
}
export default function createForm(WrappedComponent) {
class Form extends Component {
constructor() {
super()
this.state = this.props.initialValues || {}
this.handleChange = this.handleChange.bind(this)
}
handleChange(name, value) {
this.setState({
[name]: value,
})
}
render() {
return (
<WrappedComponent
{...this.state}
{...this.props}
// pass anything you want to add here
onChange={this.handleChange}
values={this.state}
/>
)
}
}
return Form
}
Then you can augment this one component as necessary (add focus, blur, submit handlers, etc). You would use it something like this:
import createForm, { Field } from './createForm'
// simplified Registration component
export class Registration extends Component {
render() {
return (
<form>
<Field
component="input"
name="name"
onChange={this.props.onChange}
type="text"
value={this.props.values.name}
/>
</form>
)
}
}
export default createForm(Registration)
You could go crazy and get into context so you don't have to manually pass values and function around, but I would stay away at least until you're more familiar with React.
Also, if you don't want to manually bind functions, you could use the class properties transform if you're using babel. Then the Form
component would just look like the following:
class Form extends Component {
state = this.props.initialValues || {}
handleChange = (name, value) => this.setState({
[name]: value,
})
render() {
return (
<WrappedComponent
{...this.state}
{...this.props}
onChange={this.handleChange}
values={this.state}
/>
)
}
}
Take a look at how it's done in NeoForm :
onChange
handler for the entire form onBlur
) and form (for example onSubmit
) validation It uses context
internally and really small and modular source code is easy to follow.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.