til

How to avoid casting [[form]].elements

typescript

When you do an uncontrolled form in React and you want to access a field inside of the elements attribute, for instance username, you'll get an error like this:

Property 'username' does not exist on type 'FormElements'

You can extend HTMLFormControlsCollection and HTMLFormElement to add all the input elements like this:

interface FormElements extends HTMLFormControlsCollection {
  username: HTMLInputElement;
}

interface UsernameFormElement extends HTMLFormElement {
  readonly elements: FormElements;
}

function UsernameForm({
  onSubmitUsername,
}: {
  onSubmitUsername: (value: string) => void;
}) {
  const [error, setError] = React.useState<string | null>(null);

  function onSubmit(evt: React.FormEvent<UsernameFormElement>) {
    evt.preventDefault();
    onSubmitUsername(evt.currentTarget.elements.username.value);
  }

  function onChange({
    currentTarget: { value },
  }: React.ChangeEvent<HTMLInputElement>) {
    const isLowerCase = value === value.toLowerCase();
    setError(isLowerCase ? null : "Username must be lower case");
  }

  return (
    <form onSubmit={onSubmit}>
      <div>
        <label htmlFor="username">Username:</label>

        <input id="username" onChange={onChange} />
      </div>

      <div role="alert" style={{ color: "red" }}>
        {error}
      </div>

      <button type="submit" disabled={Boolean(error)}>
        Submit
      </button>
    </form>
  );
}

That way you avoid casting the elements object.

You can play with a working example here

I learnt this from @kentcdodds