Four ways of capturing form data with React.js

Introduction

Dealing with form values is a common task faced by many developers as they're trying to track and store input values from a user to ultimately send them to the server, for example. Here I list four different ways we can use to tackle this with React.js library. So imagine this arbitrary form consisting of only two inputs (name and surname) and we need to handle what the user input.

1. A state to each form field

In this approach, we use the useState hook to set the value and track the changes on both inputs. Here we have an example of a controlled input, which means we take its internal state and delivered it to React to manage. If we were dealing with a big form with lots of input fields, this wouldn't be the best approach since with every keystroke on a form field the entire form would rerender because of the changing state.

import { useState } from "react";

export default function App() {
  const [name, setName] = useState("");
  const [surname, setSurname] = useState("");

  function submitHandler(e) {
    e.preventDefault();

    console.log(name, surname);
  }

  return (
    <form onSubmit={submitHandler}>
      <label htmlFor="name">Name</label>
      <input
        type="text"
        id="name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />

      <label htmlFor="surname">surname</label>
      <input
        type="text"
        id="surname"
        value={surname}
        onChange={(e) => setSurname(e.target.value)}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

2. A single state to all form fields

Almost the same approach as the one above, only in here we're using a single piece of state to handle and store every input field. This approach has the same downside as the one above, rerendering at every state change. Look at how we set the new state at every input in the changeHandler function, we spread (copy) the previous state and only rewrite the value of the current input field being typed based on its name and value.

import { useState } from "react";

export default function App() {
  const [person, setPerson] = useState({
    name: "",
    surname: "",
  });

  function changeHandler(e) {
    setPerson({
      ...person,
      [e.target.name]: e.target.value,
    });
  }

  function submitHandler(e) {
    e.preventDefault();
    const { name, surname } = person;

    console.log(name, surname);
  }

  return (
    <form onSubmit={submitHandler}>
      <label htmlFor="name">Name</label>
      <input type="text" id="name" name="name" onChange={changeHandler} />

      <label htmlFor="surname">Surname</label>
      <input type="text" id="surname" name="surname" onChange={changeHandler} />
      <button type="submit">Submit</button>
    </form>
  );
}

3. Using the FormData() constructor

Here we're using the FormData() constructor to get the key/value pairs of each form field. To do so, we need to reference the form by passing it to the FormData constructor the event.target. Once instantiate the object, we have access to FormData methods. We use the get() method to get the form fields one by one by passing the value of their attribute name. The FormData constructor allows us to do much more than getting a single form field, you can check it out more at the MDN docs.

export default function App() {
  function submitHandler(e) {
    e.preventDefault();

    const formData = new FormData(e.target);
    const name = formData.get("name");
    const surname = formData.get("surname");

    console.log(name, surname);
  }

  return (
    <form onSubmit={submitHandler}>
      <label htmlFor="name">Name</label>
      <input type="text" id="name" name="name" />

      <label htmlFor="surname">Surname</label>
      <input type="text" id="surname" name="surname" />
      <button type="submit">Submit</button>
    </form>
  );
}

4. Using refs with useRef hook

This one works by capturing the typed values of the referenced inputs. To reference an input, we need to add the ref attribute to it and attach the reference we previously created with the useRef hook. The argument passed to the useRef hook in this case is the initial value of the form field. The useRef hook returns an object with the current property representing the HTML element, and since value is a property of an <input /> element, then we capture its value by accessing its value property.

import { useRef } from "react";

export default function App() {
  const nameRef = useRef(null);
  const surnameRef = useRef(null);

  function submitHandler(e) {
    e.preventDefault();
    const name = nameRef.current.value;
    const surname = surnameRef.current.value;

    console.log(name, surname);
  }

  return (
    <form onSubmit={submitHandler}>
      <label htmlFor="name">Name</label>
      <input type="text" ref={nameRef} id="name" />

      <label htmlFor="surname">Surname</label>
      <input type="text" ref={surnameRef} id="age" />
      <button type="submit">Submit</button>
    </form>
  );
}

Conclusion

In this article, you learned four different ways to capture form data without a third-party library, each one with its perks and downsides. If you want to have more control over how your form behaves based on the user input (e.g. validation, disabling/enabling submit button) you can choose the controlled input option by giving your inputs a React state. But if you don't want to rerender your form component to every state change, you could opt to use the FormData interface or go to the useRef hook.