DEV Community

Cover image for Decluttering React Form Logic
Adam Nathaniel Davis
Adam Nathaniel Davis

Posted on • Updated on

 

Decluttering React Form Logic

React gives you a lot of control over the display and processing of form data. But this control comes at a price: You also have to write more code to manage that control. But this can be frustrating, because so much of a programmer's day is spent searching for a way to provide the same functionality with less code.

I'm going to illustrate a technique you can use to reduce the repetitive logic around form inputs. But first, let's look at some "typical" code you might see around controlled and uncontrolled components.

Uncontrolled Components

Uncontrolled components "feel" the most like old-fashioned HTML elements. We don't have to manually update the value inside the form field after the user types something. But we still have to manually grab the value after every new entry if we want to reference that value somewhere else. A super-simple uncontrolled example would look like this:

export default function App() {
  const [youTyped, setYouTyped] = useState("");

  const onChange = event => setYouTyped(event.currentTarget.value);

  return (
    <>
      <TextField
        defaultValue={""}
        label={"Email:"}
        onChange={onChange}
        required={true}
        type={"email"}
        variant={"outlined"}
      />
      <div style={{ marginTop: 50 }}>You typed: {youTyped}</div>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

This works... pretty well. The text field behaves like a "normal" HTML input field in that it will auto-update itself as the user types. And with the onChange() function, we can grab each new value as the user types, allowing us to do further processing.

But there are some definite drawbacks to this approach.

First, assuming that we want to have an ongoing reference to the most-recently entered value, we need to always remember to add that onChange() event. Otherwise, it becomes laborious to grab the value of the nested <input> field, in real-time, via old-skool methods like inputProps and document.getElementById().

Second, notice that we annotated the field as being of type={'email'}. We also declared it as required={true}. And yet, when we type in the field, or tab out of it, there is no validation indicated on the field itself to tell us whether the input is valid.

The <TextField> component in Material UI provides a convenient means by which we can tell the component whether it should display in an error state. But for that to work, we have to constantly tell it whether to do so.

That code would look something like this:

export default function App() {
  const [showError, setShowError] = useState(false);
  const [youTyped, setYouTyped] = useState("");

  const onChange = event => {
    setShowError(!event.currentTarget.validity.valid);
    setYouTyped(event.currentTarget.value);
  };

  return (
    <>
      <TextField
        defaultValue={""}
        error={showError}
        label={"Email:"}
        onChange={onChange}
        required={true}