Forms are a crucial part of almost all applications. At least one of them is usually necessary: the “Sign in” page. In this article, we are going to explain the benefits of uncontrolled forms in React and how to do it as simple as possible to re-use it in every form. We are going to use the classic “Sign in” page as an example.

signIn

Difference between controlled and uncontrolled

To understand what “uncontrolled” means, first, we’ll see the meaning of “controlled”.

A common mistake in React is to try to control every single field of a form using a state and an onChange method. This way is usually chosen to allow the use of this state inside the onSubmit method, although it’s not the only and best way to get the fields.

controlled fields

<form onSubmit={onSignIn}>

      <div>
        <input
          required
          value={this.state.username}
          onChange={e => this.setState({ username: e.target.value )}
          name="username"
          type="text"
          placeholder={userNamePlaceholder}
        />
      </div>

      <div>
        <input
          value={this.state.password}
          onChange={e => this.setState({ password: e.target.value )}
          name="password"
          type="password"
          placeholder={passwordPlaceholder}
        />
      </div>

      <button type="submit">
         Sign In
      </button>
  </form>

 

Then we can use the state directly in the onSignIn method.

onSignIn = () => {
  const { username, password } = this.state;
  // ...
}

 

These fields are controlled because every time that the state changes, the text rendered changes inside the input. Moreover, every time that the user types, the onChange event is fired to save the new state. If we type a username of 15 characters and a password of 8; 24 react renders will happen under the hood (one for each character + one extra for the first render).

This controlled behavior is useful if the state is used before submitting the form. For example, to validate dynamically the fields. Otherwise, if we want to use all the fields after submitting the form, it will be more useful to do it uncontrolled.

Uncontrolled fields are the natural way to write without a React state:

uncontrolled input

<form onSubmit={onSignIn}>

      <div>
        <input
          required
          name="username"
          type="text"
          placeholder={userNamePlaceholder}
        />
      </div>

      <div>
        <input
          name="password"
          type="password"
          placeholder={passwordPlaceholder}
        />
      </div>

      <button type="submit">
         Sign In
      </button>
  </form>

 

In this case, the state is not necessary. We need these fields on the event onSubmit but it’s not necessary to store it at every change in the React state because we already have it in the event. This means that we only do 1 simple render for this component: The first render.

On the onSignIn function, we can find the username and password fields inside event.target.

onSignIn = (event) => {
      const [username, password] = Array.prototype
          .slice.call(event.target)
          .map(field => field.value)

  // ...
}

 

However, although we simplified it a little, it’s still quite ugly to repeat this Array.prototype.slice.call in every single form submit. Let’s see how to improve it.

Improving the uncontrolled way

Our goal here is to simplify the logic of every “submit” event in order to avoid the need of finding continuously the fields inside the event.target. We want something more enjoyable like:

onSignIn = ({ username, password }) => {
 // ...
}

 

In this case, we will provide the fields directly as an argument. This argument is an object with all the fields of the form when the key is the name attribute.

To achieve our goal, we can replace the form tag to our personal Component:

form

Our reusable personal Form Component could be:

function Form({ children, onSubmit, ...restOfProps }) {
  const onSubmitAllFields = useCallback(event => {
    event.preventDefault()
    event.stopPropagation()

    const fields = Array.prototype.slice.call(event.target)
      .filter(field => field.name)
      .reduce((form, { name, value }) => ({
        ...form,
        [name]: typeof value === 'string'
          ? value.trim()
          : value,
      }), {})

    onSubmit(fields)
  }, [onSubmit])

  return (
    <form {...restOfProps} onSubmit={onSubmitAllFields}>
      {children}
    </form>
  )
}

export default memo(Form)

 

Thus, we are moving the repeating code that we always do in our forms: preventDefault, stopPropagation, extract fields + trim string fields.

Now, we can use this new approach by only changing one character, from “form” to “Form”.

Note: I’m using the new hooks API (proposal), even though it can also be written as a class component.

Conclusions

Both approaches; controlled and uncontrolled forms are great for different reasons. We have to know the difference to choose the best for any occasion. My advice would be: use normally uncontrolled unless you really need the state to do dynamic checks or to change dynamically the text of each input.

 

Load More Related Articles
Load More By john
Load More In Javascript

Leave a Reply

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

Check Also

Monetizing early-stage open-source projects

Image courtesy of Dev Open-source projects monetization has been in discussion for a long …