Sergio Xalambrí
June 19, 2019

# Working with Forms in React without libraries

Learn how to work with forms and form elements in React without using another library.

Handling forms in JavaScript could be a difficult task, in this article we will learn how to tame them.

## Uncontrolled Input

First we need to talk about uncontrolled inputs, where I say input it's also select or textarea. This is the default state of an input, in this case we do nothing special and let the browser handle the value of it.

    function Form() {
      const [message, setMessage] = React.useState("");

      function handleSubmit(event) {
        event.preventDefault();
        setMessage(event.target.elements.message.value);
        event.target.reset();
      }

      return (
        <>
          <p>{message}</p>
          <form onSubmit={handleSubmit}>
            <input name="message" type="text" />
          </form>
        </>
      );
    }

As we can see in the example above we update our state `message` with the value of the input after the user submit the form, press `enter`, and to reset the input value we just reset the whole form using the `reset()` methods of the forms.

This is normal DOM manipulation to read the value and reset it, nothing special of React.

## Controlled Input

Now let's talk about the interesting part, a controller input/select/textarea is an element where the value is bound to the state and we need to update the state to update the input value the use see.

    function Form() {
      const [message, setMessage] = React.useState("");

      function handleSubmit(event) {
        event.preventDefault();
        setMessage("");
      }

      function handleChange(event) {
        setMessage(event.target.value);
      }

      return (
        <>
          <p>{message}</p>
          <form onSubmit={handleSubmit}>
            <input
              name="message"
              type="text"
              onChange={handleChange}
              value={message}
            />
          </form>
        </>
      );
    }

Our example set the `input` value to `message` and attached a `onChange` event listener we call `handleChange`, inside this function we need the `event.target.value` where we will receive the new value of the input, which is current value plus what the user typed, and we call `setMessage` to update our component state, this will update the content of the `p` tag and the value of the `input` tag to match the new state.

If we want to reset the input we could call `setMessage("")`, as we do in `handleSubmit`, and this will reset the state and doing so the input's value and the `p` content.

## Adding a Simple Validation

Now let's add a simple validation, complex validations are similar but with more rules, in this case we will make the input invalid if the special character `_` is used.

    function Form() {
      const [message, setMessage] = React.useState("");
      const [error, setError] = React.useState(null);

      function handleSubmit(event) {
        event.preventDefault();
        setError(null);
        setMessage("");
      }

      function handleChange(event) {
        const value = event.target.value;
        if (value.includes("_")) setError("You cannot use an underscore");
        else setError(null);
        setMessage(value);
      }

      return (
        <>
          <p>{message}</p>
          <form onSubmit={handleSubmit}>
            <input
              id="message"
              name="message"
              type="text"
              onChange={handleChange}
              value={message}
            />
            {error && (
              <label style={{ color: "red" }} htmlFor="message">
                {error}
              </label>
            )}
          </form>
        </>
      );
    }

We create two states, one for the input value and another of the error message. As before inside our `handleSubmit` we will reset the `message` state to an empty string and additionally we will reset the `error` state to `null`.

In the `handleChange` we will read the new value of the input and see if the underscore is there. In case we found an underscore we will update the error state to the message `"You cannot use an underscore"` if it's not there we will set it to `null`. After the validation we will update the `message` state with the new value.

In our returned UI we will check of the presence of an `error` and render a `label` with text color red pointing to the input and showing the error message inside. The error is inside a label to let the user click it and move the focus to the input.

## Controlling a Textarea

Before I said working with `input` and `textarea` was similar, and it actually is, let's change the element we render to a `textarea`, our code above will continue to work without any other change as we could see below.

    function Form() {
      const [message, setMessage] = React.useState("");
      const [error, setError] = React.useState(null);

      function handleSubmit(event) {
        event.preventDefault();
      }

      function handleChange(event) {
        const value = event.target.value;
        if (value.includes("_")) {
          setError("You cannot use an underscore");
        } else {
          setError(null);
          setMessage(value);
        }
      }

      return (
        <>
          <p>{message}</p>
          <form onSubmit={handleSubmit}>
            <textarea
              id="message"
              name="message"
              onChange={handleChange}
              value={message}
            />
            {error && (
              <label style={{ color: "red" }} htmlFor="message">
                {error}
              </label>
            )}
          </form>
        </>
      );
    }

While usually `textarea` is an element with internal content as `<textarea>Content here</textarea>` in React to change the value we use the `value` prop like an inputs and the `onChange` event, making the change between input and textarea similar.

## Controlling a Select

Now let's talk about the `select`. As with the `textarea` you treat it as a normal `input`, pass a `value` prop with the selected value and listen to value changes with `onChange`. The value passed to the `select` should match the value of one of the options to show one of them as the currently selected option.

    function Form() {
      const [option, setOption] = React.useState(null);
      const [error, setError] = React.useState(null);

      function handleSubmit(event) {
        event.preventDefault();
      }

      function handleChange(event) {
        setOption(event.target.value);
      }

      function handleResetClick() {
        setOption(null);
      }

      function handleHooksClick() {
        setOption("hooks");
      }

      return (
        <>
          <p>{option}</p>
          <form onSubmit={handleSubmit}>
            <select onChange={handleChange} value={option}>
              <option value="classes">Classes</option>
              <option value="flux">Flux</option>
              <option value="redux">Redux</option>
              <option value="hooks">Hooks</option>
            </select>
          </form>
          <button type="button" onClick={handleResetClick}>
            Reset
          </button>
          <button type="button" onClick={handleHooksClick}>
            Hooks!
          </button>
        </>
      );
    }

## Working with File Inputs

Now to finish let's talk about the file input, this special input can't be controlled, but it's still possible to get some data and save it in the state to show it elsewhere. In the example below we are creating a custom UI for a hidden file input.

    function Form() {
      const [fileKey, setFileKey] = React.useState(Date.now());
      const [fileName, setFileName] = React.useState("");
      const [fileSize, setFileSize] = React.useState(0);
      const [error, setError] = React.useState(null);

      function resetFile() {
        setFileKey(Date.now());
        setFileName("");
        setFileSize(0);
        setError(null);
      }

      function handleChange(event) {
        const file = event.target.files[0];

        setFileSize(file.size);

        if (file.size > 100000) setError("That file is too big!");
        else setError(null);

        setFileName(file.name);
      }

      return (
        <form>
          <label htmlFor="file">
            Select a single file to upload. (max size: 100kb)
            <br />
            {fileName && (
              <>
                <strong>File:</strong> {fileName} ({fileSize / 1000}kb)
              </>
            )}
            <input id="file" type="file" key={fileKey} onChange={handleChange} style={{ display: "none" }} />
          </label>
          {error && (
            <label style={{ color: "red" }} htmlFor="file">
              {error}
            </label>
          )}
          <button type="button" onClick={resetFile}>
            Reset file
          </button>
        </form>
      );
    }

We listen to the change event and read the file size and name and validate the size of the file, if it's too big we set the `error` state to the message `"That file is too big!"`, if the file is not that big we will set the error to `null`, this let us remove the previous error if the user selected a big file before.

We also have a button to reset the input, since we can't control the state we could use the `key` to force React render the input again and resetting it in the process, we use the current date and every time the user click on `Reset file` it will get the current date and save it to the `fileKey` state and reset it input.