Transitioning from uncontrolled inputs to controlled

25 Jul 2017

Transitioning from uncontrolled inputs to controlled

You know about the differences between the controlled vs. uncontrolled form inputs.

You may have started out with uncontrolled form inputs — which is perfectly fine!

It turns out your requirements have got more sophisticated. You need to disable that submit button or instantly validate your fields, perhaps.

And for that, you need your inputs to be controlled.

So how do you go from uncontrolled to controlled?

The transition is straightforward. You would need to:

  1. Identify all form controls — textboxes, selects, checkboxes.
  2. Initialize the state for each of these.
  3. Make these controls “get” their value from this.state.
  4. Make these controls “set” new values to this.state — using the onChange prop.
  5. Change your submit handler to get values from the state, instead of refs.

Suppose you had a form with uncontrolled inputs, something like this:

class UncontrolledForm extends Component {
  handleSubmit = () => {
    const email = this._emailInput.value;
    const agreeCheckbox = this._agreeCheckbox.checked;
    ...
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input type="email" ref={inp => this._emailInput = inp} />
        <input type="checkbox" ref={inp => this._agreeCheckbox = inp} />
      </form>
    );
  }
}

1. Identify all form controls

As simple as it sounds, you are just taking a mental (or paper, or code) note of what your form has. It’s important that you give each control a meaningful name. “Email field” instead of just “a text field”.

In the example above, the form controls are:

2. Initialize the state for these

You now have a list of what’s inside your form. The next step would be to start storing the values of these things in this.state, instead of relying on the browser to store these for us in DOM.

Don’t worry about how these will be used or updated just yet.

Continuing the example, we would need the state to contain:

In code, we would set it in the constructor like this:

class ControlledForm extends Component {
  constructor() {
    super();
    this.state = {
      email: '',
      agree: false,
    };
  }
}

3. Make the controls “get” their values from state

We are now kind of storing the values of these controls inside this.state. Now it’s time to tell our inputs to use these values.

class ControlledForm extends Component {
  // constructor omitted

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input type="email" value={this.state.email} />
        <input type="checkbox" checked={this.state.agree} />
      </form>
    );
  }
}

Why’s it that we are using checked for the checkbox, but value for the text inputs? What would you use for textareas, or selects, or radio buttons? Refer to the “What makes an input controlled” section of Controlled vs. uncontrolled inputs in React.

4. Make the controls “set” their values to state

An input that shows a static value and cannot be changed isn’t of much value.

So we need to allow the inputs to update the state back.

If you need a refresher, here’s an excerpt from Controlled vs. uncontrolled inputs in React:

Every time you type a new character, handleNameChange is called. It takes in the new value of the input and sets it in the state.

  • It starts out as an empty string — ''.

  • You type a and handleNameChange gets an a and calls setState. The input is then re-rendered to have the value of a.

  • You type b. handleNameChange gets the value of ab and sets that to the state. The input is re-rendered once more, now with value="ab".

This flow kind of ‘pushes’ the value changes to the form component, so the Form component always has the current value of the input, without needing to ask for it explicitly.

This means your data (state) and UI (inputs) are always in sync. The state gives the value to the input, and the input asks the Form to change the current value.

We are going to write an event handler for every input, and pass it as the onChange prop.

class ControlledForm extends Component {
  // contructor omitted

  handleEmailChange = (evt) => {
    this.setState({ email: evt.target.value });
  };

  handleAgreeChange = (evt) => {
    this.setState({ agree: evt.target.checked });
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input onChange={this.handleEmailChange} />
        <input type="email" value={this.state.email} onChange={this.handleEmailChange} />
        <input type="checkbox" checked={this.state.agree} onChange={this.handleAgreeChange} />
      </form>
    );
  }
}

5. Change your submit handler to get values from state, instead of refs

Now for the easiest part, instead of reading from refs in handleSubmit, we will simply get from the state.

class ControlledForm extends Component {
  handleSubmit = () => {
    const email = this.state.email;
    const agree = this.state.agree;
    ...
  };
}

All in all, the new, controlled form would look like this:

class ControlledForm extends Component {
  constructor() {
    super();
    this.state = {
      email: '',
      agree: false,
    };
  }

  handleSubmit = () => {
    const email = this.state.email;
    const agree = this.state.agree;
    ...
  };

  handleEmailChange = (evt) => {
    this.setState({ email: evt.target.value });
  };

  handleAgreeChange = (evt) => {
    this.setState({ agree: evt.target.checked });
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input onChange={this.handleEmailChange} />
        <input type="email" value={this.state.email} onChange={this.handleEmailChange} />
        <input type="checkbox" checked={this.state.agree} onChange={this.handleAgreeChange} />
      </form>
    );
  }
}

And we can now have the most up-to-date values right in the state, enabling us to do much more than uncontrolled inputs would allow.

Think your friends would dig this article, too?

Google+
Tumblr
Preview

Know how you tear your hair out when your designer asks to make a nice form?

But what if you could implement the form experience your users deserve?

The Missing Forms Handbook of React can help you breeze through your React forms. You will learn how forms fit into React and see how to implement common form patterns.

The excerpt contains a table of contents and two chapters: on building an intuition for forms and handling various form controls.

No spam. I will occasionally send you my posts on JavaScript and React.
Preview

Know how you tear your hair out when your designer asks to make a nice form?

But what if you could implement the form experience your users deserve?

The Missing Forms Handbook of React can help you breeze through your React forms. You will learn how forms fit into React and see how to implement common form patterns.

The excerpt contains a table of contents and two chapters: on building an intuition for forms and handling various form controls.