Tuesday, 28 February, 2017 UTC


Summary

In this short article, you will get a chance to learn what controlled components are and why they are beneficial.
Suppose a component has an input field with a value depending on the state of the component. The default value of the component is placed in its state. We will also implement a button to clear the name. This button will be linked to an event handler setting the state.name property to an empty string.
import React from 'react';

class ControlledInput extends React.Component {
    constructor( props, context ) {
        super( props, context );
        this.state = {
            name: 'Zsolt'
        }
    }
    handleClear() {
        this.setState( { name: '' } );
    }
    render() {
        return (
            <div>
                <input value={this.state.name} />
                <button onClick={ () => this.handleClear() } >
                    Clear
                </button>
            </div>
        ); 
    }
};

ReactDOM.render(
    <ControlledInput />,
    document.querySelector( '.js-app' )
);
Notice the registration of the onClick callback of the button. This shorthand enables us to save context binding in the constructor. The onClick handler is a simple function that calls handleClear. The arrow function automatically performs context binding. Recall:
input => returnValue;
is equivalent to
function( input ) { return returnValue; }.bind( this );
As a consequence, handleClear will be called with the correct context.
Play around with the example in CodePen. What did you notice?
The default value of the name is displayed perfectly. The clear button also works, clearing the input field value. There is just one tiny problem: the input field does not react to any user action. When you start typing, the value of the input field cannot be modified.
This is because the value of the input field is controlled by the state of the component. As typing in the component does not change the state, React makes sure that the value of the component is kept in sync with the unchanged state.

Editing the textfield

In order to make the textfield editable, we have to set the state of the input field on change.
import React from 'react';

class ControlledInput extends React.Component {
    constructor( props, context ) {
        super( props, context );
        this.state = {
            name: 'Zsolt'
        }
    }
    handleClear() {
        this.setState( { name: '' } );
    }
    handleNameChange( e ) {
        this.setState( { name: e.target.value } );
    }
    render() {
        return (
            <div>
                <input value={this.state.name} 
                       onChange={ e => this.handleNameChange( e ) } />
                <button onClick={ () => this.handleClear() } >
                    Clear
                </button>
            </div>
        ); 
    }
};

ReactDOM.render(
    <ControlledInput />,
    document.querySelector( '.js-app' )
);
If you try out the code, you can see that the value of our component became editable.
This is good news, as the value of the textfield is
  1. editable,
  2. accessible through the state of the component,
  3. reflects the state of the component regardless of what causes the state to change.
You will often encounter controlled components in React.

Controlled Components in ChatBox

If you recall our ChatBox example from this article, you can probably identify a candidate that could be made a controlled component.
Check out the source code and try to rewrite it as a controlled component by forking my CodePen implementation.
The component you will need to rewrite is PostMessageForm.
class PostMessageForm extends React.Component {
    constructor( props, context ) {
        super( props, context );

        this.handleSubmit = this.handleSubmit.bind( this );
    }
    handleSubmit( event ) {
        event.preventDefault();
        this.props.appendChatMessage( this.nameInput.value, this.messageInput.value );
        this.nameInput.value = '';
        this.messageInput.value = '';    
    }   
    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <input type="text" 
                       ref={name => this.nameInput = name}
                       placeholder="Name" />
                <input type="text" 
                       ref={message => this.messageInput = message}
                       placeholder="Message" />
                <input type="submit" value="Send" />
            </form>
        );
    }
}
Making this component controlled involves the following changes:
  1. Set the initial state in the constructor
  2. Make the values of the input fields dependent on the state
  3. Register event handlers to the input fields by changing the state of the component on changing the value of the input fields
  4. Change the handleSubmit implementation to access and modify the state of the component instead of the input field values
  5. Remove the unnecessary refs
The result looks like this:
class PostMessageForm extends React.Component {
    constructor( props, context ) {
        super( props, context );

        this.state = {
            name: '',
            message: ''
        }

        this.handleSubmit = this.handleSubmit.bind( this );
    }
    handleSubmit( event ) {
        event.preventDefault();
        this.props.appendChatMessage( this.state.name, this.state.message );
        this.setState( {
            name: '',
            message: ''
        } );  
    }   
    handleNameChange( e ) {
        this.setState( { name: e.target.value } );
    }
    handleMessageChange( e ) {
        this.setState( { message: e.target.value } );
    }
    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <input type="text" 
                       value={this.state.name}
                       onChange={ e => this.handleNameChange( e ) }
                       placeholder="Name" />
                <input type="text" 
                       value={this.state.message}
                       onChange={ e => this.handleMessageChange( e ) }
                       placeholder="Message" />
                <input type="submit" value="Send" />
            </form>
        );
    }
}
If you try out this solution in CodePen, you can see that this code works in the exact same way as before.
If there are no changes in behavior, why did we bother changing our perfectly working component to a controlled component? You will find out the answers in the next section on the benefits of controlled components.

Benefits of controlled components

When controlling user interfaces, we have to get used to the fact that users trigger events all the time. Some events may trigger changes in other fields. For instance, when you press the submit button, the value of text fields are cleared. We may imagine other scenarios such as disabling an input field and clearing its value whenever we uncheck a checkbox. Handling these knock-on effects becomes a lot easier by linking the value of our fields to the state of the component.
Synchronizing the state works in both directions. Both the user and the internal state controls the component. In one direction, external events may change the value of our fields by changing the state. In the other direction, the user may change the value of our field, triggering a state change, which in turn may trigger additional knock-on effects in other components.
With fully controlled components, you don’t have to keep track of a spaghetti code of knock-on effects, as React handles the two-way synchronization between the component state and the field values.
The value of your components also becomes a lot easier to access using state. You don’t need refs anymore to extract or modify the value of your input fields. You can simply rely on getting and setting the state of your component, react does the rest for you.
As a consequence, controlled components become more easily testable. All you need to do is read and write the state and observe the changes in the markup.
One more benefit is that you can easily set the default value of your fields in one place, namely in the constructor of your component. The default values don’t have to be present in the markup as input attributes.
If you have child components relying on the state of your component, you can simply pass down your state as props. These lower level components are often presentational components consisting of a simple render method.
In the next React article, we will continue with exploring the difference between stateful and stateless functional components. The latter ones will be mainly used for presentational purposes.