The only topic that wasn't covered till now is, how react handles HTML form elements? It is very common to collect data from a user like login information, search queries, filter data, feedback etc... For all these cases we use differnt HTML form elements like input, text area, select dropdown, checkboxes, radio buttons, multi-select and so on.
All HTML form elements come with their own state, meaning no one tells them how to update their values when a user interacted with them.
React
<form class="row">
<div class="col-sm-6">
<div class="form-group">
<label htmlFor="name">Name:</label>
<input type="text" class="form-control" id="name" />
</div>
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>
In React typically mutable state is maintained using this.state
and it gets updated using this.setState
method.
The solution here is to achieve the default behaviour of HTML form elements with their state maintained by React. We call them Controlled Components.
var container1 = document.getElementById('container-1');
class MyInput extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.inputChanged = this.inputChanged.bind(this);
}
inputChanged(e) {
this.setState({
name: e.target.value
});
}
render() {
return (
<div>
<input type="text" className="form-control"
value={this.state.name} onChange={this.inputChanged}/>
<p>{this.state.name}</p>
</div>
)
}
}
ReactDOM.render(<MyInput />, container1);
Here input value is a prop
, so the only way to update the input value is using a callback in this case using onChange
Controlled components will be very benificial if we need to perform any tasks on user inputs or for form validations.
var container2 = document.getElementById('container-2');
var countries = [
"India",
"Australia",
"United States",
"Russia",
"China"
]
class MyForm extends React.Component {
constructor(props) {
super(props);
this.state = {
form: {
name: '',
gender: 'male',
country: '',
sports: []
}
};
this.nameChanged = this.nameChanged.bind(this);
this.genderSelected = this.genderSelected.bind(this);
this.countrySelected = this.countrySelected.bind(this);
this.sportsSelected = this.sportsSelected.bind(this);
}
nameChanged(e) {
const {form} = this.state;
form.name = e.target.value;
this.setState({ form });
}
genderSelected(e) {
const {form} = this.state;
form.gender = e.target.value;
this.setState({ form });
}
countrySelected(e) {
const {form} = this.state;
form.country = e.target.value;
this.setState({ form });
}
sportsSelected(e) {
const selectedSport = e.target.value;
const {sports} = this.state.form;
const index = sports.indexOf(selectedSport);
if(index > -1) {
sports.splice(index, 1);
}else {
sports.push(selectedSport);
}
const {form} = this.state;
form.sports = sports;
this.setState({ form });
}
render() {
const {form} = this.state;
return (
<div className="row">
<div className="col-sm-12">
<form className="form-horizontal">
<div className="form-group">
<label for="name" className="control-label col-xs-2">Name</label>
<div className="col-xs-9">
<input type="text" className="form-control"
id="name" placeholder="Name"
value={form.name} onChange={this.nameChanged} />
</div>
</div>
<div className="form-group">
<label for="gender" className="control-label col-xs-2">Gender</label>
<div className="col-xs-9">
<label className="radio-inline">
<input type="radio" name="gender" value="male"
checked={form.gender === 'male'} onChange={this.genderSelected}/> Male
</label>
<label className="radio-inline">
<input type="radio" name="gender" value="female"
checked={form.gender === 'female'} onChange={this.genderSelected}/> Female
</label>
</div>
</div>
<div className="form-group">
<label for="gender" className="control-label col-xs-2">Country</label>
<div className="col-xs-9">
<select className="form-control" name="country"
value={form.country}
onChange={this.countrySelected}>
<option value="">Select</option>
{
countries.map((country, index) => {
return <option value={country}>{country}</option>
})
}
</select>
</div>
</div>
<div className="form-group">
<label for="gender" className="control-label col-xs-2">Sports</label>
<div className="col-xs-9">
<label className="checkbox-inline">
<input type="checkbox" name="sports"
checked={form.sports.indexOf("cricket") > -1} value="cricket"
onChange={this.sportsSelected}/> Cricket
</label>
<label className="checkbox-inline">
<input type="checkbox" name="sports"
checked={form.sports.indexOf("football") > -1} value="football"
onChange={this.sportsSelected}/> Football
</label>
<label className="checkbox-inline">
<input type="checkbox" name="sports"
checked={form.sports.indexOf("tennis") > -1} value="tennis"
onChange={this.sportsSelected}/> Tennis
</label>
</div>
</div>
<div className="form-group">
<div className="col-xs-offset-2 col-xs-9">
<button type="submit" className="btn btn-primary">Submit</button>
</div>
</div>
</form>
</div>
<div className="col-sm-12">
{JSON.stringify(this.state.form)}
</div>
</div>
)
}
}
ReactDOM.render(<MyForm />, container2);
Using controlled components is a tedious task, for every form element there should be a corresponding event handler and after sometime your component might endup with unnecessary code. As I said earlier use Controlled components only when you need control over form element input values or if validations require otherwise you can go with uncontrolled components.
Refs
ref
is a special attribute provided by react which accepts a function and gives you a reference for underlying DOM node.
In the typical React dataflow, props are the only way that parent components interact with their children. To modify a child, you re-render it with new props. However, there are a few cases where you need to imperatively modify a child outside of the typical dataflow. The child to be modified could be an instance of a React component, or it could be a DOM element. For both of these cases, React provides an escape hatch.
Uncontrolled components
In most cases, we recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.
To write an uncontrolled component, instead of writing an event handler for every state update, you can use a ref to get form values from the DOM.
var container3 = document.getElementById('container-3');
class MyForm extends React.Component {
constructor(props) {
super(props);
this.form = {}
this.onFormSubmit = this.onFormSubmit.bind(this);
}
onFormSubmit(e) {
e.preventDefault();
alert(this.form.name.value);
}
render() {
return (
<div className="row">
<div className="col-sm-12">
<form className="form-horizontal" onSubmit={this.onFormSubmit}>
<div className="form-group">
<label for="name" className="control-label col-xs-2">Name</label>
<div className="col-xs-9">
<input type="text" className="form-control"
id="name" placeholder="Name"
ref={(name) => (this.form.name = name) } />
</div>
</div>
<div className="form-group">
<div className="col-xs-offset-2 col-xs-9">
<button type="submit" className="btn btn-primary" >Submit</button>
</div>
</div>
</form>
</div>
</div>
)
}
}
ReactDOM.render(<MyForm />, container3);