0

I want to define an object that holds a pair of event handler name and event handler using React and TypeScript so I can pass around an array of objects like

{
    eventHandlerName: 'onBlur',
    eventHandler: (e: React.FocusEvent<HTMLInputElement>) => { /* something */},
}

I've tried defining this type as

type SpecifiedEventHandler<I, E extends keyof React.DOMAttributes<I>> = {
    eventHandlerName: E,
    handlerFunction: React.DOMAttributes<I>[E];
};

So as to call it like

const myObject: SpecifiedEventHandler<HTMLInputElement, keyof React.DOMAttributes<HTMLInputElement>>[] = {
    [
        {
            eventHandlerName: 'onBlur',
            handlerFunction: (e: React.FocusEvent<HTMLInputElement>) => { /* something */},
        },
        /* more here */
    ]
};

But the problem is that this is not restrictive enough. I can pass 'foobar' as the handlerFunction and it compiles just fine.

What am I missing here? The key is that I want the handlerFunction to have the type of the field in React.DOMAttributes<HTMLInputElement> that corresponds by name to the eventHandlerName.

Thanks!

Michael Tontchev
  • 909
  • 8
  • 23

1 Answers1

1

The problem is that the type of an array element is SpecifiedEventHandler<HTMLInputElement, keyof React.DOMAttributes<HTMLInputElement>> meaning that E will be keyof React.DOMAttributes<HTMLInputElement>> so handlerFunction will be React.DOMAttributes<HTMLInputElement>[keyof React.DOMAttributes<HTMLInputElement>>] so basically ANY value that can be a the value of a property of React.DOMAttributes<HTMLInputElement> which will be a lot of types including {} so any type will be compatible with handlerFunction.

If you specify a more restrictive type for E you will get the desired error, but probably not the desired functionality of having an array of several event types :

const myObject: SpecifiedEventHandler<HTMLInputElement, 'onBlur'>[] =
    [
        {
            eventHandlerName: 'onBlur',
            handlerFunction: '(e: React.FocusEvent<HTMLInputElement>) => { /* something */},'
        },
    ]; // Error
const myObjectOk: SpecifiedEventHandler<HTMLInputElement, 'onBlur'>[] =
    [
        {
            eventHandlerName: 'onBlur',
            handlerFunction: (e: React.FocusEvent<HTMLInputElement>) => { /* something */},
        },
    ]; // Ok

You could create a helper function to validate each entry in the array, you would need to manually call it for every item:

function event<I, E  extends keyof React.DOMAttributes<I>>(event: SpecifiedEventHandler<I, E>) : typeof event {
    return event;
}

const myObject: SpecifiedEventHandler<HTMLInputElement, keyof React.DOMAttributes<HTMLInputElement>>[] =
    [
        // Fun trick I is inferred based on return type 
        event({ 
            eventHandlerName: "onBlur",
            handlerFunction: (e: React.FocusEvent<HTMLInputElement>) => { /* something */},
        }),
        // Error eventHandlerName not ok 
        event({ 
            eventHandlerName: "onBlur2",
            handlerFunction: (e: React.FocusEvent<HTMLInputElement>) => { /* something */},
        }),
        // Error handlerFunction not ok 
        event({ 
            eventHandlerName: "onBlur",
            handlerFunction: '(e: React.FocusEvent<HTMLInputElement>) => { /* something */}',
        }),
    ];
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • Yeah, I think I reached that conclusion too :( So there's no way to make the array type generic enough that it will cover the arbitrary case where the event type would be different between elements? Any idea whether any future TS features might enable this? – Michael Tontchev May 08 '18 at 22:28
  • @MichaelTontchev A variable can't have a generic argument, and I don't know of any proposed feature to allow this. You can improve the `function` approach using something similar to this https://stackoverflow.com/questions/48872328/infer-tuple-type-instead-of-union-type/48872543#48872543. Namely have multiple overloads so that each `eventHandlerName` will have a dedicated generic argument. If you want I can provide a working function. – Titian Cernicova-Dragomir May 09 '18 at 05:02