Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question about VoidableEventCallback #60

Open
crimx opened this issue Jul 16, 2019 · 6 comments
Open

Question about VoidableEventCallback #60

crimx opened this issue Jul 16, 2019 · 6 comments

Comments

@crimx
Copy link

crimx commented Jul 16, 2019

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

export type VoidableEventCallback<EventValue> = EventValue extends void ? () => void : (e: EventValue) => void

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?

@Brooooooklyn
Copy link
Contributor

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)

@crimx
Copy link
Author

crimx commented Jul 16, 2019

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

@Brooooooklyn
Copy link
Contributor

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.

@crimx
Copy link
Author

crimx commented Jul 16, 2019

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).

@Austaras
Copy link

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

@crimx
Copy link
Author

crimx commented Sep 25, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants