Bug: Typescript definitions don't let me define function types that may return Destructors

2
closed
DavidIAm
DavidIAm
Posted 3 months ago

Bug: Typescript definitions don't let me define function types that may return Destructors #22536

Move Destructor into the React namespace so my typescript can use it.

React version: 17.0.2

Steps To Reproduce

I want to create functional hook components that replace commonly duplicated code - for instance, I am commonly using my event bus.

// somewhere...
const AppContext = createContext<{ eventbus: EventEmitter }>({ eventbus: new EventEmitter() })
// elsewhere...
const { eventBus } = useContext(AppContext)
useEffect(() => {
  if (!eventBus) return
  if (!otherThing) return
  if (someStateThing) eventBus.emit("setup", otherThing.bit(someStateThing))
  return (): void => void eventBus.emit("cleanup", otherThing.bit(someStateThing))
}, [ eventBus, otherThing, someStateThing ] )

But I want to compose something I can use like this:

useEffectWithEventBus((eventBus) => {
  if (!otherThing) return
   eventBus.emit(otherThing.bit(someStateThing))
   (): void => void eventBus.emit("cleanup", otherThing.bit(someStateThing))
}, [otherThing, someStateThing])

But when I go to try and define that

const useEffectWithEventBus: (cb: (eventbus: EventEmitter) => void | Destructor, deps: DependencyList) => void = (cb, deps) => {
   const { eventBus } = useContext(AppContext) 
   useEffect(() => cb(eventBus), [ eventBus, ...deps ])
}

Cannot find name Destructor

But EffectCallback IS () => void | Destructor

And destructor is type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };

Its not inside the React Namespace - Destructor is mysteriously limited to the File scope of the react index? So I can't define my custom hook to have a function that can return a destructor... because I don't have access to the Destructor type.

I thought, well, maybe I can just do functional composition on that... but... how do I define my callback out of the scope where eventBus is pulled from the context? I might be able to get it in there functionally by using the .call(this) context...

const useEffectWithEventBus: (cb: EffectCallback, deps: DependencyList) => void = (cb, deps) => {
   const { eventBus } = useContext(AppContext) 
   useEffect(() => cb.call(eventBus), [ eventBus, ...deps ])
}
useEffectWithEventBus(() => {
  if (!otherThing) return
   this?.emit(otherThing.bit(someStateThing))
   (): void => void this?.emit("cleanup", otherThing.bit(someStateThing))
}, [otherThing, someStateThing])

But then the compiler thinks that this might be undefined (true) and it has no idea that this refers to an EventEmitter - and it eludes me how(or even if is possible) to type narrow this to an EventEmitter. (that's pretty advanced typing don't you think?)

The current behavior

Destructor type not exported enough to use in my code

The expected behavior

Destructor type is exported enough to use in my code

markerikson
markerikson
Created 3 months ago

FYI, the React team does not maintain the React TS typedefs. Those are maintained by the community, over in DefinitelyTyped:

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/types/react/react.d.ts

DavidIAm
DavidIAm
Created 3 months ago

Gah. Oops. I work with so many packages that are more integrated. I'll copypasta this over there...