Introduce to Loose-Constrolled react form input

React provides two ways to use its form inputs, Controlled and Uncontrolled.

I will try to explain both of them and the user cases, after that I will introduce an in-the-middle way to control your form input: Loos-Controlled, which tries to fulfill the user case that the others do not solve well.

Uncontrolled form input

An uncontrolled form input is an input that is not controlled, which means it has its state to manage its current value coordinate to user’s interaction.

A form input without the property value is a untrolled form input:

It will behave just like a normal form control, you can type anything you can, and it will updates as you type.

In most cases you may want to specified a default value for the input to have when it is rendered the first time:

Uncontrolled form input works quite well in most cases; you only need to add an event handler to update your app state from the user input:

Controlled form input

In some user cases, you need to take a full control of the value of the input.

Image you are asked to build a search bar with a suggestion list, like Google search:

ee39ab2b635f7e4879d137e56a430002

According to the design your designer gave it to you, you are considering all the functionalities:

  1. User should be able to input text freely
  2. Suggestions will show according to the current user input
  3. User can use arrow key or mouse click to choose a suggestion to make the input change automatically

For features (1) and (2), you can use an uncontrolled form input to implement it easily, but you will fail when you try to implement (3), like:

The logic in this code snap looks good. First, using an uncontrolled form input you leave the user to type whatever he wants, (1) is done. Then, by handling onChange, you update state.value to the latest user input, and you pass the value to <Suggestion /> so that it can update its suggestions, which achieves (2). You also give the state.value back to input using defaultValue={this.state.value}, but this time, it failed.

Since it is an uncontrolled form, so defaultValue only effect when the input is rendered at the first time.

So what you need is a controlled form input, and you need to use value instead of defaultValue:

That’s it! The only difference is just replacing defaultValue to value. You may still not quite clear why this is called “controlled“.

Change the code a little bit (remove the onChange handler):

And now open the page and type to type something into the input!

Wherever you type does not go into the input! (let’s assume the initial value of this.state.value is empty). Why? Because you specify the value of the input should be this.state.value:

Which means the value of the input is controlled by this.state.value, it will always equal to this.state.value unless you change it, and since we remove the handler for onChange, so the state never gets the chance to update, that’s whatever you type does not work.

How to choose?

The choice between uncontrolled and controlled form input is quite simple:

  • In some cases, if you need to update the value according to some business logic instead of user’s direct input, use controlled form input.
  • Otherwise, use uncontrolled form input.

Not everyone is happy

Everything looks good already, any other problem?

Let’s get back to the suggestion example.

In a real application, your suggestions may not come from your local, but from a remote HTTP request, and every time user update the value a new request will be sent out to get the latest suggestions.

It sounds quite normal, but think about it: a programmer who want to search something about Javascript, so he is going to type Javascript, there actually will be multiple requests sending out with all the queries:

  • J
  • Ja
  • Jav
  • Java
  • Javas
  • Javasc

Too much right? A better way will be that we set a small delay to send the query. Whenever a change happens, we don’t send out a new request directly. Instead, we wait for a certain short time, and if within the short time, another change comes, we get rid of the previous change, and wait for another short period. If no change comes within the time limit, we send out the request.

According to how long the delay you set, the sent out request queries will be reduced to like:

  • J
  • Java
  • Javascr
  • Javascript

Much better, right? OK let’s keep on working the code, we use the debounce from Lodash to implement this kind of delay call:

this line of code implemented the delay:

Which will cause if multiple calls of this.handleOnChange happens within 100ms, only the last one will be executed.

Looks great! Now you open the page, and try to type something, things become wired again!

After you type, the value of the input will take a 100ms delay to update!

Why? Because you debounce the change handler, which delayed the update of this.state.value.

So in order to make the update of input value immediately and delay the update of the <Suggestions />‘s query, you perhaps change you code to:

By doing this, the value of the input will update immediately, and the update for this.state.query will be delayed!

But still several extra lines of code to write!

What if…, what if we can have some kind of input, that can be input freely, but also can update its value when a new value is specified?

Loose-Controlled form input.

Let’s image what the behaviors of a loose-controlled form input will have:

  • Will act just as an uncontrolled form input. Whenever you type something, it will update immediately!
  • When you need to specify a specific value at any moment, you can specify it by giving a value prop, and it will update itself!
  • After update with a specific value, if the user continues to type, it will update immediately again.

As you can see, the behavior is kind of in the middle of controlled and uncontrolled! It is perfect for most of the user cases; now you can rewrite our suggestions example like this:

It will save you a lot of code to manipulate state for input values, and make your code much cleaner.

Let’s Build a Loose-Controlled Component

The implementation of a loose-controlled form input is quite easy, you just need to three things:

  • Use value to fully control the input element you are going to wrap with this.state.value
  • Get default value for this.state.value from this.props.value or this.props.defaultValue, this make this element can also be used just as a uncontrolled input.
  • update this.state.value in componentWillReceiveProps whever this.props.value is presented.
  • update this.state.value whenever the input changes, this is the key to make the component

Leave a Comment

Your email address will not be published. Required fields are marked *