React: Form Example

In React when we say a form input is "Controlled" this means that the value of the input is controlled by the state of the component. This is in contrast to an "Uncontrolled" input where the value is controlled by the DOM.

Inside of my <form> I always like to wrap the entire contents with a fieldset. this way I can disable the entire form easily by setting the disabled attribute on the fieldset.

<fieldset disabled={loading} aria-busy={loading}>

first we need to create some states:

  const [form, setForm] = useState(formInitialState);
  const [showConfirm, setShowConfirm] = useState(false);
  const [loading, setLoading] = useState(false);
  const [success, setSuccess] = useState(false);
  const [fail, setfail] = useState(false);

Lets create a handleReset, handleSetForm, and a submit function:

  const handleReset = () => {
    setForm(formInitialState);
    setfail(false);
    setSuccess(false);
  };

  const handleSetForm = ({ target: { name, value } }) => {
    setForm((form) => ({ ...form, [name]: value }));
  };

  const confirm = (e) => {
    e.preventDefault();
    setShowConfirm(true);
  };

  const submit = async (e) => {
    e.preventDefault();
    setLoading(true);
    console.log({ form });
    // simulate send api request
    await timeout(parseInt(form.timeout, 10), form.resolve)
      .then((res) => {
        handleReset();
        confetti();
        console.log(res);
      })
      .catch((res) => {
        console.log("error");
        setfail(true);
        setSuccess(false);
        console.log(res);
      })
      .finally(() => {
        setLoading(false);
        setShowConfirm(false);
      });
  };

and now for our form. here we will give each input a name this name is used in our handleSetForm to know what input to update. we also give each input a value this value is used to set the value of the input. we also give each input an onChange handler this is used to update the state of the form. Finally on our form we give it an onSubmit handler this is used to submit the form.

<form
  onSubmit={confirm}
  onReset={handleReset}
  className={cn(success && "success", fail && "fail")}
>
  <fieldset disabled={loading} aria-busy={loading}>
    <input
      placeholder="first"
      value={form.first}
      name="first"
      onChange={handleSetForm}
    />
    <input
      placeholder="last"
      value={form.last}
      name="last"
      onChange={handleSetForm}
    />
    <input
      placeholder="email"
      value={form.email}
      name="email"
      onChange={handleSetForm}
    />
    <div className="input-group">
      <label htmlFor="timeout">timeout (ms):</label>
      <input
        id="timeout"
        placeholder="timeout"
        value={form.timeout}
        name="timeout"
        onChange={handleSetForm}
        type="number"
      />
    </div>

    <select
      placeholder="resolve"
      value={form.resolve}
      name="resolve"
      onChange={handleSetForm}
    >
      <option value={false}>resolve</option>
      <option value={true}>reject</option>
    </select>
    <div>
      <button type="submit">Submit</button>
      <button type="reset">Reset</button>
    </div>
  </fieldset>
</form>

Validation

Look into using zod to validate data from your forms. This will allow you to validate the data before sending it to the server: zod.dev

A great way to keep this organized is with conform: conform.guide

There are also other libraries like react-hook-form, formik, and final-form that can help with validation.

Demo

CodeSandbox

Resources