Home > OS >  How to prevent repeated casting of HTMLFormElement in TypeScript
How to prevent repeated casting of HTMLFormElement in TypeScript

Time:11-28

Currently I'm looping over the elements of a form using the following code, but I'm wondering how I could eliminate the repeated casts as HTMLFormElement. If I exclude them I get a warning that the Property 'type' does not exist on type 'Element'.

Array.from((e.target as HTMLFormElement).elements)
  .filter(el => (el as HTMLFormElement).type === 'radio')
  .filter(el => (el as HTMLFormElement).checked === true)

While I can hoist up the cast to an Array, such as:

const els = Array.from((e.target as HTMLFormElement).elements) as Array<HTMLFormElement>
els
  .filter(el => el.type === 'radio')
  .filter(el => el.checked === true)

It seems seems like I should be able to simply define/assert the type rather than needing to cast it.

Is there a reason this doesn't work equally as well?

const els: Array<HTMLFormElement> = Array.from((e.target as HTMLFormElement).elements

Full context is a React component.

export const Form = ({ children }: FormProps) => {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
    e.preventDefault()

    const els = Array.from((e.target as HTMLFormElement).elements)
    els.filter(el => el instanceof HTMLInputElement && el.type === 'radio' && el.checked === true)
  }

  return (
    <form onSubmit={handleSubmit}>
      {children}
      <br />
      <button type="submit">Submit</button>
    </form>
  )
}

CodePudding user response:

The repeated assertions are superfluous because you only need a single .filter callback. Also, the .elements collection will be composed of control elements, like inputs and buttons - not HTMLFormElement (which only the containing <form> is) - two of your as are asserting inaccurately.

You can get rid of the assertions entirely by testing in the JavaScript whether the element being iterated over is an <input> (which is safer).

Array.from((e.target as HTMLFormElement).elements)
    .filter(el => el instanceof HTMLInputElement && el.type === 'radio' && el.checked === true);

Is there a reason this doesn't work equally as well?

Well, it would need to be typed properly, to start with - but it'd be easier to let TypeScript infer the type automatically (a generic Element type, as it does now, is fine) and not bother trying to correct it.

Since it sounds like this is in an event handler, if the element the listener is attached to is typed properly, you could refer to it again instead of having to cast e.target.

const form = document.querySelector('form')!;
form.addEventListener('submit', () => {
    Array.from(form.elements)
        .filter(el => el instanceof HTMLInputElement && el.type === 'radio' && el.checked === true);
});

though if there's more than one form on the page, you'll need a generic

const form = document.querySelector<HTMLFormElement>('form')!;

If the resulting filtered array needs to be typed, the only option is to be somewhat repetitive by adding as Array<HTMLInputElement> to the end, or by making .filter generic.

  • Related