React-Champ: Part I: Controlled vs Uncontrolled Components

Adarsh Singh
4 min readOct 12, 2018

--

Handling user input is one of the basic functionality needed in almost all WebApps. Here, I show you a pattern in React which helps you handle user input in a way that makes it less prone to bugs.

React Champ: Part I

For Champs: Hold your horses! I’ll also discuss(in React Champ: Part II) where it makes sense to use uncontrolled components too.

Controlled Components” let you maintain the state of the input fields in the state. But the catch is, it makes sure that the state is the single source of truth for data as well as UI.

Advantages of using controlled components

  1. The state becomes the single source of truth for both the data and the UI.
  2. Makes debugging easier for bug-detection.
  3. Makes it easier to “lift the state up”, which in turn, makes it easier to break your components into functional and presentational components(Pure Components!).
  4. Easier to implement on-the-fly(onChange) validations.
  5. Easier to ensure inputs are in the specific format, like telephone numbers.

Difference between controlled and uncontrolled components

Uncontrolled components remember what you typed(or clicked or changed) on the form in DOM while the controlled components keep that in the component’s state(or any data store you use, such as Redux).

How to implement a controlled component?

  1. Declare the initial state of the form
  2. The value will be retrieved from the state.
  3. Set value={name}(for text fields) and onChange props to the input
  4. The OnChange function will take the changed value and update the state(setState) with it.
  5. React re-renders the input with the changed value(Done by React after setState).

You can find the code for controlled components in this repo(Branch: controlled-components)

Step 1: Declare the initial state of the form

constructor(props) {
super(props);
this.state = {
name: '',
age: 26,
selectedGender: '',
socialMedia: [...SOCIAL_MEDIA_LIST] // An Array
};
}

Note: Don’t worry about the three dots(Spread operator) in the below code. It is an ES6/ES2015 feature. It just instantiates a new array and puts all the contents of “SOCIAL_MEDIA_LIST” array in it. Like creating a copy of the array with a new reference.

Step 2 and 3: Extract value(=name) from state and set value and onChange props to the input

render() {
const {name, age, selectedGender} = this.state;
...
<FormControl type="text" value={name} placeholder="Name" onChange={(e) => this.handleChange(e.target.value, 'name')}/>
}

Assign the name(destructured from this.state) to value prop and an onChange function.

Step 4: Update name in the state

handleChange = (value, key) => {
this.setState({
[key]: value
}
}

Step 5: Done by React itself

This part is done by React itself. Once the state is updated, render function is called again and the UI is synced with the updated state. If you do any F-ups anywhere, the UI will not get updated and you’ll most probably find the bug in the development stage itself(when you can’t see anything you type on the screen). To test this, remove value prop from the input field and try typing in the text field again.

Controlling other inputs like Checkboxes and Radio input

class ControlledComponentForm extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
age: 26,
selectedGender: '',
socialMedia: [...SOCIAL_MEDIA_LIST]
};
}
handleChange = (value, key) => {
this.setState({
[key]: value
});
}
handleSocialMediaChange = (index) => {
let {socialMedia} = this.state;
socialMedia[index]['selected'] = !socialMedia[index]['selected'];
this.handleChange([...socialMedia], 'socialMedia');
}
handleSubmit = () => {
const {
name, age, selectedGender, socialMedia
} = this.state;
const formData = {
name,
age,
gender: selectedGender,
socialMedia: socialMedia.map(socialMediaItem => socialMediaItem.id)
}
console.log('Post Form Data: ', formData);
}
render() {
const {name, age, selectedGender} = this.state;
return (<div>
<Row>
<Col md={12}>
<FormGroup>
<ControlLabel>Name</ControlLabel>
<FormControl type="text" value={name} placeholder="Name" onChange={(e) => this.handleChange(e.target.value, 'name')}/>
</FormGroup>
</Col>
</Row>
<Row>
<Col md={12}>
<FormGroup>
<ControlLabel>Gender</ControlLabel>
<br />
{
GENDER_LIST && GENDER_LIST.map(gender => {
return (<Radio
key={gender.id}
inline={true}
onChange={() => this.handleChange(gender.id, 'selectedGender')}
checked={selectedGender === gender.id}>
{gender.name}
</Radio>)
})
}
</FormGroup>
</Col>
</Row>
<Row>
<Col md={12}>
<FormGroup>
<ControlLabel>Age</ControlLabel>
<FormControl type="number" min={1} value={age} placeholder="Age" onChange={(e) => this.handleChange(e.target.value, 'age')}/>
</FormGroup>
</Col>
</Row>
<Row>
<Col md={12}>
<FormGroup>
<ControlLabel>Social Media</ControlLabel>
<br />
{
SOCIAL_MEDIA_LIST && SOCIAL_MEDIA_LIST.map((socialMedia, socialMediaIndex) => {
return (<Checkbox
key={socialMedia.id}
inline={true}
onChange={() => this.handleSocialMediaChange(socialMediaIndex)}
checked={socialMedia.selected}>
{socialMedia.name}
</Checkbox>)
})
}
</FormGroup>
</Col>
</Row>
<Row>
<Col md={12}>
<Button bsStyle="primary" onClick={this.handleSubmit}>Submit</Button>
</Col>
</Row>
</div>);
}
}export default ControlledComponentForm;

Implementing OnChange Validation

In the same example as above, if we want to stop the user from submitting the form unless the user’s name is non-empty, we can easily do this by the following code

<Button disabled={!(name.length > 0)} bsStyle="primary" onClick={this.handleSubmit}>Submit</Button>
Before: When “name” is empty, “Submit” is disabled
After: When “name” is non-empty, “Submit” is enabled

Similarly, you can add more on-the-fly validations easily. Also, you can ensure that the input is in specified format by checking the value before updating the state.

This is a very small detail in large scheme of things. But this small practice can free you from bugs arising out of out-of-sync state in React. This also makes it easier to debug your application too as the complete truth resides in the state object.

Please feel free to comment and share your feedback.

Happy Coding!

Next in Series:

React-Champ: II: To control or not to control(components)

--

--