Search code examples
reactjstypescripteslinttypescript-eslint

Unexpected any when passing callback to third-party library


I have some React+TypeScript code that makes use of a component from a third party library.

<ThirdPartyComponent onSelect={(value: any) => {...}} />

eslint-typescript is identifying this as an issue:

Unexpected any. Specify a different type. eslint(@typescript-eslint/no-explicit-any)

The type definition of the select handler uses any:

export interface ThirdPartySelectCallback extends React.EventHandler<any> {
  (value: any, e: React.SyntheticEvent<{}>): void;
}

What is the proper way to avoid this issue without disabling the eslint rule?


Changing the type to string gives a different error:

<ThirdPartyComponent onSelect={(value: string, e: React.SyntheticEvent<{}>) => {...}} />

Type '(value: string, e: SyntheticEvent<{}, Event>) => void' is not assignable to type 'ThirdPartySelectCallback'


Solution

  • You can use ThirdPartyComponent like this:

    // add the 2nd parameter e?: React.SyntheticEvent<{}>
    const App = () => (
      <ThirdPartyComponent
        onSelect={(v: string, e?: React.SyntheticEvent<{}>) => {}}
      />
    )
    
    const ThirdPartyComponent = (props: { onSelect: ThirdPartySelectCallback }) => (
      <div>Hello package</div>
    )
    

    Some explanation

    React.EventHandler<any> as well as ThirdPartySelectCallback are function type interfaces. When one function interface extends another, for each function parameter the union of the types is formed (because function types are contravariant in their parameters). Have a look at this Playground for an easy example of this merging.

    So let's take your example again:

    // React.EventHandler - eased up a bit here
    type EventHandler<E extends SyntheticEvent<any>> = (event: E): void 
    
    interface ThirdPartySelectCallback extends React.EventHandler<any> {
      (value: any, e: React.SyntheticEvent<{}>): void;
    }
    
    • First parameters are E and any. E | any becomes effectively any.
    • Second parameters are undefined (first function has no second parameter) and React.SyntheticEvent<{}>, so its merged to React.SyntheticEvent<{}> | undefined.

    So in the end, you can specify the following function for ThirdPartySelectCallback:

    (v: any, e?: React.SyntheticEvent<{}>) => void
    

    , where v can be literally of any type like string to solve your eslint error.