TypeScript: override and fix typing definitions for packages
Asked Answered
S

1

11

I'm using the React victory library for charts, and I'm using TypeScript. I have added the @types/victory package to my project, but unfortunately, it's missing a bunch of key definitions that I need. For some interfaces, it was missing a couple of properties, so (using this answer), I created a custom file to add those properties to the interfaces.

But, now I have a problem: the interface defined for events has a property named eventHandlers defined, but again it's missing an important definition, how it's defined:

export interface EventPropTypeInterface<TTarget, TEventKey> {
  /* other properties */,
  eventHandlers: {
    [key: string]:
      { (event: React.SyntheticEvent<any>): EventCallbackInterface<TTarget, TEventKey> } |
      { (event: React.SyntheticEvent<any>): EventCallbackInterface<TTarget, TEventKey>[] }
  }

But, the problem is that the function should allow accepting a second parameter, example (I'm not sure about the type of the second argument, so I have any):

{ (event: React.SyntheticEvent<any>, dataPoint: any): EventCallbackInterface<TTarget, TEventKey> }

So, I tried to include this property in my own definition:

declare module "victory" {
  export interface EventPropTypeInterface<TTarget, TEventKey> {
  eventHandlers: {
    [key: string]:
      { (event: React.SyntheticEvent<any>, dataPoint: any): EventCallbackInterface<TTarget, TEventKey> } |
      { (event: React.SyntheticEvent<any>, dataPoint: any): EventCallbackInterface<TTarget, TEventKey>[] }
  }
}

But now, TypeScript is complaining about the index.d.ts in the @typings directory, saying that eventHandlers must be of the type I have defined.

Is there a way to use the provided type definition file and somehow "augment" it with my own definition similar to what I did with other interfaces when just adding new properties?

Scarecrow answered 13/2, 2018 at 16:50 Comment(3)
I asked a similar question a few days ago, and gist of the answer was "properties in definition files can't be redefined": https://mcmap.net/q/784000/-how-can-i-augment-a-property-within-a-third-party-typescript-interface-defined-as-quot-any-quot. But I would be happy if this was incorrect!Jez
Does it just allow another parameter instead of require one? If so, maybe change it to optional, like dataPoint?: any instead of dataPoint: any.Misgiving
@Misgiving I had the same idea, unfortunately, it doesn't seem to work at all.Changeless
C
5

This can work. The problem is that the types were not defined with extensibility in mind.

Object literal types, such as {x: string} are great in many situations, but not for complex nested structures.

The reason is that these types, similar to type aliases, are not subject to the declaration merging needed to enable extensibility.

If the types of the possible values of the eventHandlers object were declared as an interface it could be extended with ease by simply adding additional overloads.

The following, self contained example shows how extensibility is quite easy when the types are refactored to use an interface declaration for the value type of eventHandlers:

declare module "victory" {
    interface EventHandler<TTarget, TEventKey> {
        (event: React.SyntheticEvent<any>): EventCallbackInterface<TTarget, TEventKey>
        (event: React.SyntheticEvent<any>): EventCallbackInterface<TTarget, TEventKey>[]
    }

    export interface EventPropTypeInterface<TTarget, TEventKey> {
        eventHandlers: {
            [key: string]: EventHandler<TTarget, TEventKey>

        }
    }
}
declare module "victory" {
    // this interface merges with the original, declaring additional overloads of the all signature
    interface EventHandler<TTarget, TEventKey> {
        // declare overloads
        (event: React.SyntheticEvent<any>, dataPoint: any): EventCallbackInterface<TTarget, TEventKey>
        (event: React.SyntheticEvent<any>, dataPoint: any): EventCallbackInterface<TTarget, TEventKey>[]
    }
}

type EventCallbackInterface<T, U> = {}
declare namespace React {
    export type SyntheticEvent<T> = {};
}

Test:

import victory = require("victory");

declare const handlerProps: victory.EventHandler<{}, {}>;

handlerProps({}, { x: 1, y: 2 }); // works fine

Here is a link to it in action

I strongly recommend that you submit a Pull Request to https://github.com/DefinitelyTyped/DefinitelyTyped that modifies npm:@types/victory to use an interface in this manner to enable such extensibility.

Changeless answered 13/2, 2018 at 17:38 Comment(4)
Thank you for your detailed answer. Although I was hoping for a way to solve the issue, based on your explanation, it's not possible. One question: why did you declare EventHandlerMap in your code? I'm not sure I understand why it's thereScarecrow
@Scarecrow nice catch was an artifact from experimenting. Removed itChangeless
Thank you for the clarification! Just one thing: can I use your code if at some point I get the time to submit a pull request?Scarecrow
You are of course most welcome to it. You may also link this answer in a PR if you find that helpfulChangeless

© 2022 - 2024 — McMap. All rights reserved.