rxjs-hooks icon indicating copy to clipboard operation
rxjs-hooks copied to clipboard

Question about VoidableEventCallback

Open crimx opened this issue 6 years ago • 6 comments

I was trying write something like

const [callback, flag] = useEventCallback<
  React.MouseEvent<HTMLElement> | boolean,
  boolean
>($events => $events.pipe(mapTo(false)), false)

callback(true)

And there was type error.

Argument of type 'true' is not assignable to parameter of type 'false & true & MouseEvent<HTMLElement, MouseEvent>'.
  Type 'true' is not assignable to type 'false'.ts(2345)

Checking the source

https://github.com/LeetCode-OpenSource/rxjs-hooks/blob/2b781f12d0c54a079cca9e419153e50e0388fcb0/src/use-event-callback.ts#L6

It seems like for example

type Callback = VoidableEventCallback<string | boolean>

will be resolved as

type Callback = ((e: string) => void) | ((e: false) => void) | ((e: true) => void)

which means e has to be string & boolean. Is this a bug or am I missing something here?

crimx avatar Jul 16 '19 13:07 crimx

It's seem like a TypeScript behavior. Maybe you should let TypeScript resolve the generic type params itself.

const [callback, flag] = useEventCallback(($events: Observable<React.MouseEvent<HTMLElement> | boolean>) => $events.pipe(mapTo(false)), false)

callback(true)

Brooooooklyn avatar Jul 16 '19 14:07 Brooooooklyn

Same result. It's the VoidableEventCallback giving the wrong types.

crimx avatar Jul 16 '19 14:07 crimx

What's version of the TypeScript in your project? I'm using [email protected] and const [callback, flag] = useEventCallback(($events: Observable<React.MouseEvent<HTMLElement> | boolean>) => $events.pipe(mapTo(false)), false) works fine.

Brooooooklyn avatar Jul 16 '19 15:07 Brooooooklyn

I'm using 3.5.2 too.

const [callback, flag] = useEventCallback(($events: Observable<React.MouseEvent<HTMLElement> | boolean>) => $events.pipe(mapTo(false)), false) is fine. It's callback(true) got the error.

Found this on the doc, might be the reason?

Conditional types in which the checked type is a naked type parameter are called distributive conditional types. Distributive conditional types are automatically distributed over union types during instantiation. For example, an instantiation of T extends U ? X : Y with the type argument A | B | C for T is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).

crimx avatar Jul 16 '19 15:07 crimx

It's a known trick of Typescript, that is when a union is put in a contravariant place(like function parameter) in conditional type it will be turned into a intersection type. So boolean = true | false is turned into true & false aka never

There is one easy and tricky solution

type VoidableEventCallback<EventValue> = {
  0: () => void
  1: (e: EventValue) => void
}[EventValue extends void ? 0 : 1]

But @Brooooooklyn what is the purpose of this type in the first place? In Typescript a function with a void parameter is quite identical to a function with no parameter

declare function foo(a: void): void
foo()
foo(1) // error

Austaras avatar Sep 25 '19 03:09 Austaras

Very insightful! Didn't know a trick like this. Wish you'd comment earlier so that I don't have to make my own wheels.

crimx avatar Sep 25 '19 03:09 crimx