Material-ui: Typescript typings for TextField are wrong

Created on 25 Sep 2017  路  7Comments  路  Source: mui-org/material-ui

version: v1-beta12

as per TextField.d.ts in v1-beta12, the typings extend FormControlProps.
This in turn extends React.HtmlHTMLAttributes<HTMLDivElement>, which eventually has property onChange(ev :React.SyntheticEvent<HTMLDivElement>) .

The problem being that React.SyntheticEvent<HTMLDivElement> is generically typed to HTMLDivElement, even though in v1-beta12, the event's currentTarget is actually an HTMLInputElement.

It would make the typings much easier to use if this was fixed.

The problem is that with the generic set to HTMLDivElement, onChange event handlers must be coded like so:

function onChange(ev : React.FormEvent<HTMLDivElement>) {
    const actualTarget = ev.currentTarget as any as HTMLInputElement
    console.log(actualTarget.value)
}
// or without the any...
function onChange(ev : React.FormEvent<HTMLElement>) {
    const actualTarget = ev.currentTarget as HTMLInputElement
    console.log(actualTarget.value)
}
typescript

Most helpful comment

The latest fix (I'm using 1.0.0-beta.16) prevents compilation with a type error.

This code

class TextInputDemo ... {
  ...
  render() {
    return (
      <TextField
        label="Text"
        value={this.state.title}
        onChange={this.handleChange}
      />
    );
  }

  private handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    ...
  }
}

fails with the following compiler type error:

error TS2322: Type '{ label: "Text"; value: string; onChange: (event: ChangeEvent<HTMLInputElement>) => void; requir...' is not assignable to type 'IntrinsicAttributes & { autoComplete?: string | undefined; autoFocus?: boolean | undefined; child...'.
  Type '{ label: "Text"; value: string; onChange: (event: ChangeEvent<HTMLInputElement>) => void; requir...' is not assignable to type 'FormControlProps'.
    Types of property 'onChange' are incompatible.
      Type '(event: ChangeEvent<HTMLInputElement>) => void' is not assignable to type '((event: FormEvent<HTMLDivElement>) => void) | undefined'.
        Type '(event: ChangeEvent<HTMLInputElement>) => void' is not assignable to type '(event: FormEvent<HTMLDivElement>) => void'.

If I change the event handler to

  private handleChange = (event: React.FormEvent<HTMLDivElement>) => {
    ...
  }
````
I get the following compiler error:

...
Types of property 'onChange' are incompatible.
Type '(event: FormEvent) => void' is not assignable to type '((event: ChangeEvent) => void) | undefined'.
Type '(event: FormEvent) => void' is not assignable to type '(event: ChangeEvent) => void'.

The only workaround at the moment is something like this:
```typescript
  private handleChange = (event: {}) => {
    const e = event as React.ChangeEvent<HTMLInputElement>;
    ...
  }

The problem lies in the typedefinition of the TextFieldProps:

export type TextFieldProps = {
  ...
  onChange?: React.ChangeEventHandler<HTMLInputElement>;
} & FormControlProps;

FormControlProps extends React.HtmlHTMLAttributes<HTMLDivElement> and somewhere deeper in the hierarchy of FormControlProps we find the following interface:

    interface DOMAttributes<T> {
        ...
        // Form Events
        onChange?: FormEventHandler<T>;
        ...
    }

And as T is of type HTMLDivElement here, we cause the conflict which makes the compiler fail.

All 7 comments

Can you please add an usage example.

From looking at the implementation of TextField inheriting from a div is correct. If you put an onChange on the TextField it will be passed to the FormControl, which is by default rendered as a div. So, It's kinda weird that the currentTarget is an input.

If you want to listen for changes on the input field, you can use the InputProps.

Whilst TextField returns a FormControl around the Input component, it doesn't attach the onChange event to the FormControl:
https://github.com/callemall/material-ui/blob/dbbcaa6e1a6ae05b87b076091985ec6e2ac97a3d/src/TextField/TextField.js#L178-L188

https://github.com/callemall/material-ui/blob/dbbcaa6e1a6ae05b87b076091985ec6e2ac97a3d/src/TextField/TextField.js#L227-L234

It attaches it to the Input component:
https://github.com/callemall/material-ui/blob/dbbcaa6e1a6ae05b87b076091985ec6e2ac97a3d/src/TextField/TextField.js#L205-L223

So whilst TextField technically is by default rooted with a div, the component you're attaching the event to is (by default) an input.

Proper React dictates that you should use the currentTarget on the event to interact with the event, NOT the target (which is why react's SyntheticEvent typings provides a useless type for target).
How can I get the value from a HTMLDivElement?

If that's your intention from an API design perspective, then you should probably provide custom events that explicitly expose the value, so consumers don't have to fight with your definitions.

My bad, you're correct. The onChange is set on the <Input>. Updated the typings accordingly.

Regarding your issue with the "API design perspective". This is OSS, so anyone is welcome to contribute ;) The people doing the TS typings are doing their best to make everyone happy.

You were a version out - in v11 it put it on the FormControl, it was changed 2 days ago (released with v12): https://github.com/callemall/material-ui/commit/b815af0d644493e85cff96644573709dc9f534f0

It's on my todo list to contribute back to this and a few other projects we're leaning on. After my current slab of work slows down.

The latest fix (I'm using 1.0.0-beta.16) prevents compilation with a type error.

This code

class TextInputDemo ... {
  ...
  render() {
    return (
      <TextField
        label="Text"
        value={this.state.title}
        onChange={this.handleChange}
      />
    );
  }

  private handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    ...
  }
}

fails with the following compiler type error:

error TS2322: Type '{ label: "Text"; value: string; onChange: (event: ChangeEvent<HTMLInputElement>) => void; requir...' is not assignable to type 'IntrinsicAttributes & { autoComplete?: string | undefined; autoFocus?: boolean | undefined; child...'.
  Type '{ label: "Text"; value: string; onChange: (event: ChangeEvent<HTMLInputElement>) => void; requir...' is not assignable to type 'FormControlProps'.
    Types of property 'onChange' are incompatible.
      Type '(event: ChangeEvent<HTMLInputElement>) => void' is not assignable to type '((event: FormEvent<HTMLDivElement>) => void) | undefined'.
        Type '(event: ChangeEvent<HTMLInputElement>) => void' is not assignable to type '(event: FormEvent<HTMLDivElement>) => void'.

If I change the event handler to

  private handleChange = (event: React.FormEvent<HTMLDivElement>) => {
    ...
  }
````
I get the following compiler error:

...
Types of property 'onChange' are incompatible.
Type '(event: FormEvent) => void' is not assignable to type '((event: ChangeEvent) => void) | undefined'.
Type '(event: FormEvent) => void' is not assignable to type '(event: ChangeEvent) => void'.

The only workaround at the moment is something like this:
```typescript
  private handleChange = (event: {}) => {
    const e = event as React.ChangeEvent<HTMLInputElement>;
    ...
  }

The problem lies in the typedefinition of the TextFieldProps:

export type TextFieldProps = {
  ...
  onChange?: React.ChangeEventHandler<HTMLInputElement>;
} & FormControlProps;

FormControlProps extends React.HtmlHTMLAttributes<HTMLDivElement> and somewhere deeper in the hierarchy of FormControlProps we find the following interface:

    interface DOMAttributes<T> {
        ...
        // Form Events
        onChange?: FormEventHandler<T>;
        ...
    }

And as T is of type HTMLDivElement here, we cause the conflict which makes the compiler fail.

@svenwiegand This was already fixed in #8618

@sebald Yeah thanks. Can confirm that this is working with 1.0.0-beta.17.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

reflog picture reflog  路  3Comments

mattmiddlesworth picture mattmiddlesworth  路  3Comments

ghost picture ghost  路  3Comments

activatedgeek picture activatedgeek  路  3Comments

ghost picture ghost  路  3Comments