Material-ui: [Tabs] Issues with onChange TypeScript types

Created on 16 Sep 2019  路  18Comments  路  Source: mui-org/material-ui

  • [x] The issue is present in the latest release.
  • [x] I have searched the issues of this repository and believe that this is not a duplicate.

Current Behavior 馃槸

Wrapping the Tabs component results in TypeScript emitting the wrong types for the onChange prop:

Type '(event: ChangeEvent<{}>, value: number) => void' is not assignable to type '((event: ChangeEvent<{}>, value: any) => void) & ((event: FormEvent<HTMLButtonElement>) => void)'.
  Type '(event: ChangeEvent<{}>, value: number) => void' is not assignable to type '(event: FormEvent<HTMLButtonElement>) => void'.ts(2322)

The same problem looks to be appearing for action. It appears to be intersecting the types from ButtonBases props. Maybe a union type was intended?

(((instance: TabsActions | null) => void) & ((instance: ButtonBaseActions | null) => void)) | (((instance: TabsActions | null) => void) & React.RefObject<ButtonBaseActions>) | (React.RefObject<...> & ((instance: ButtonBaseActions | null) => void)) | (React.RefObject<...> & React.RefObject<...>) | null | undefined

Expected Behavior 馃

The type of TabsProps.onChange should be ((event: React.ChangeEvent<{}>, value: any) => void) | undefined. I believe this regression may have been caused by #16487.

Steps to Reproduce 馃暪

See https://codesandbox.io/s/upbeat-grass-gc0i9 for the TypeScript error and example wrapping of Tabs component.

Context 馃敠

It's quite common for us to wrap up the MUI components with our own customizations and export them as common components to use throughout our applications. In this case after upgrading MUI's minor version recently this started causing TypeScript errors everywhere we've used Tabs.

Your Environment 馃寧

| Tech | Version |
| ----------- | ------- |
| Material-UI | v4.4.2 |
| React | v16.9.0 |
| Browser | |
| TypeScript | v3.6.3 |
| etc. | |

Tabs typescript

Most helpful comment

I'm also getting the following error:

TS2322: Type '(event: React.ChangeEvent<{}>, newValue: string) => void' is not assignable to type '((event: ChangeEvent<{}>, value: any) => void) & ((event: FormEvent<HTMLButtonElement>) => void)'. 聽聽Type '(event: React.ChangeEvent<{}>, newValue: string) => void' is not assignable to type '(event: FormEvent<HTMLButtonElement>) => void'

I think the key part of this error is the expected type: ((event: ChangeEvent<{}>, value: any) => void) & ((event: FormEvent<HTMLButtonElement>) => void)

The & should be an | it seems as there's no valid function that would match both of those function signatures at the same time that I can see.

All 18 comments

I think there's actually a lot more going on across many components. I'm encountering the same issue in a number of places. For example using Link:

function onClick(event: React.MouseEvent<HTMLButtonElement>) {}

return (<Link component="button" onClick={onClick}>
  Hey!
</Link>);

has error:

Type '(event: MouseEvent<HTMLButtonElement, MouseEvent>) => void' is not assignable to type '((event: MouseEvent<HTMLAnchorElement, MouseEvent>) => void) & ((event: MouseEvent<HTMLElement, MouseEvent>) => void)'.
  Type '(event: MouseEvent<HTMLButtonElement, MouseEvent>) => void' is not assignable to type '(event: MouseEvent<HTMLAnchorElement, MouseEvent>) => void'.
    Types of parameters 'event' and 'event' are incompatible.

Sample: https://codesandbox.io/s/create-react-app-with-typescript-ztfq4

/cc @ypresto

Updated title to reflect that the issue isn't just within Tabs.

@ianschmitz I can't reproduce the issue on your codesandbox. Can you?

I'm still seeing in my sandbox in the OP on latest MUI (4.7.1).

image

I'm also getting the following error:

TS2322: Type '(event: React.ChangeEvent<{}>, newValue: string) => void' is not assignable to type '((event: ChangeEvent<{}>, value: any) => void) & ((event: FormEvent<HTMLButtonElement>) => void)'. 聽聽Type '(event: React.ChangeEvent<{}>, newValue: string) => void' is not assignable to type '(event: FormEvent<HTMLButtonElement>) => void'

I think the key part of this error is the expected type: ((event: ChangeEvent<{}>, value: any) => void) & ((event: FormEvent<HTMLButtonElement>) => void)

The & should be an | it seems as there's no valid function that would match both of those function signatures at the same time that I can see.

Having the same error, does anyone have a better fix/hack for it than this?

handleChange = compose((event: React.ChangeEvent<{}>) => {
      // do nothing.
    }, (event: React.FormEvent<HTMLButtonElement>, newValue: any) => {
      setValue(newValue);
});

We are also having this issue
Our (temporary) solve is to use

import Tabs, { TabsProps } from ""@material-ui/core/Tabs"
const HnTabs: typeof Tabs = (props: TabsProps) => <Tabs {...props} />;

But we'd like a better solution, this doesn't feel good

@ianschmitz I have seen the exact same bug in #20191. What do you think of this patch https://github.com/mui-org/material-ui/issues/20191#issuecomment-601467065?

@oliviertassinari I thought i was going crazy here is a sandbox showing 2 approaches both breaking something and fixing something else.

@ianschmitz I have seen the exact same bug in #20191. What do you think of this patch #20191 (comment)?

@oliviertassinari a quick glance _seems_ like that would improve things. To be honest some of those types make me go cross eyed when i read them so it's hard to say without writing out a bunch of test cases 馃槢

I'm just doing this right now. It's messy but I don't really know a better solution

interface TabsPropsFixed extends Omit<TabsProps, "onChange"> {
  onChange:
    | ((event: ChangeEvent<{}>, value: any) => void)
    | ((event: FormEvent<HTMLButtonElement>) => void);
}
export const Tabs: React.FC<TabsPropsFixed> = props => <Tabs {...props}/>

Good news: The suggested type was definitely wrong on our part.
Bad news: React.ChangeEvent is not the (technically) correct type (Though React.ChangeEvent<{}> will work incidentally. It's only valid structurally but has implications that don't hold true such as event.target === event.currentTarget.)

We're not creating a custom, react change event in the Tabs component. We're simply passing along whatever event triggered the change in value. I won't go into detail what specific type of events this could be so that we're not needlessly restricting our implementation.

It's a bit unfortunate since it's highly unlikely that you ever relied on specifics of ChangeEvent simply because they don't matter in our case (has something to do with target vs. currentTarget).

I suggest using function handleTabsChange(event: React.SyntheticEvent, tabsValue: unknown) {} since that is going to be the type in v5.

@eps1lon I still do have the issue. I tried both solutions, nothing works for me.

[email protected]

Screen Shot 2020-09-06 at 14 10 43
Screen Shot 2020-09-06 at 14 10 17

UPD: I just realised that this fix was merged into next branch that is 5.* alpha. Is it possible to have this fix cherry-picked to 4.* by any chance?

Is it possible to have this fix cherry-picked to 4.* by any chance?

We're only backporting important or security fixes at this stage.

@eps1lon I still do have the issue. I tried both solutions, nothing works for me.

[email protected]

Screen Shot 2020-09-06 at 14 10 43

Screen Shot 2020-09-06 at 14 10 17

UPD: I just realised that this fix was merged into next branch that is 5.* alpha. Is it possible to have this fix cherry-picked to 4.* by any chance?

Were you able to figure this out?

function handleTabsChange(event: React.SyntheticEvent, tabsValue: unknown) {}

This worked for me

Still have this issue, and when I am reading "event.target.value" or "event.currentTarget.value" there is "undefined" or "null".

Still have this issue, and when I am reading "event.target.value" or "event.currentTarget.value" there is "undefined" or "null".

export interface ITabsProps extends Omit {
children?: React.ReactNode;
textColor?: TabsProps['textColor'];
onChange: (event: React.ChangeEvent<{}>, newValue: number) => void;
}

@iiskaandar I used this

Was this page helpful?
0 / 5 - 0 ratings