How to work with form elements in typescript
Asked Answered
L

4

12

I'd like to access form elements via myForm.elements, and then access each element by it's name, for example, myForm.elements.month. Typescript doesn't like this b/c it doesn't know that form.elements contains a property of month. I thought, let's create an interface! So I did, (see code below), but I'm getting this typescript error: Neither type 'HTMLCollection' nor type 'FormElements' is assignable to the other

Here's the code I'm working with:

interface FormElements {
    day: HTMLInputElement;
    month: HTMLInputElement;
    year: HTMLInputElement;
}

class BirthdateInput {
    constructor(form: HTMLFormElement) {
        var elements: FormElements = <FormElements> form.elements; // error here

        this.day = elements.day;
        this.month = elements.month;
        this.year = elements.year;
    }
}

Any ideas on how to better cast my form.elements object so typescript won't complain?

Lafreniere answered 27/4, 2015 at 22:35 Comment(0)
A
6

Best way would be to write it like this:

// Note 'extends' clause here
interface FormElements extends HTMLFormElement {
    day: HTMLInputElement;
    month: HTMLInputElement;
    year: HTMLInputElement;
}

class BirthdateInput {
    constructor(form: HTMLFormElement) {
        var elements: FormElements = <FormElements> form.elements; // OK
        // ...
Apostate answered 27/4, 2015 at 22:37 Comment(2)
FormElements actually corresponds to form.elements, not the form itself, so it's better to extend from HTMLCollection, or possibly HTMLFormControlsCollection than HTMLFormElement. But the extends aspect is correct.Lafreniere
if you are going to do this in .tsx. You have to use as keyword instead. var elements: FormElements = form.elements as FormElements;Weatherby
E
6

Origin answer

Create an interface extending HTMLFormControlsCollection or HTMLCollection and add there your inputs

interface FormElements extends HTMLFormControlsCollection {
    day: HTMLInputElement
    month: HTMLInputElement
    year: HTMLInputElement
}

Eventually, for example, getting this

interface FormElements extends HTMLFormControlsCollection {
    day: HTMLInputElement
    month: HTMLInputElement
    year: HTMLInputElement
}
function onSearch(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    const elements = event.currentTarget.elements as FormElements

    // ...
  }

However

You can create a helper so you don't need to write an interface for each form every time

type FormElements<U extends string> = HTMLFormControlsCollection & Record<U, HTMLInputElement>

And use it like this

const elements = event.currentTarget.elements as FormElements<"id" | "name" | "type" | "amount">

Eventually, for example, getting this

function onSearch(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    const elements = event.currentTarget.elements as FormElements<"id" | "name" | "type" | "amount">

    // ...
  }
Expunction answered 5/2, 2022 at 7:7 Comment(0)
L
5

Turns out adding an extends clause fixes it:

interface FormElements extends HTMLCollection {
    day: HTMLInputElement;
    month: HTMLInputElement;
    year: HTMLInputElement;
}
Lafreniere answered 27/4, 2015 at 22:57 Comment(0)
S
0

Whether right or wrong, I have found that you can also do something like this:

interface FormElements {
    day: HTMLInputElement;
    month: HTMLInputElement;
    year: HTMLInputElement;
}

class BirthdateInput {
    constructor(form: HTMLFormElement) {
        var elements: FormElements = <FormElements>(<any> form.elements); 

        this.day = elements.day;
        this.month = elements.month;
        this.year = elements.year;
    }
}
Swag answered 25/8, 2015 at 11:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.