Trying to define a type for an array of handler tuples
useEventHandlers([
(document, "pointermove", (e: Dom.pointerEvent) => ()),
(document, "pointerup", (e: Dom.pointerEvent) => ()),
(document, "keyup", (e: Dom.keyboardEvent) => ()),
])
But I am running into an error about how the keyboardEvent does not match the pointerEvent of the other two.
What I’ve tried:
Adding a generic type type events<'a> = array<(Dom.element, string, 'a => unit)>
but it’s the same issue. I’m thinking that’s because array has to be the generic means that each function uses that same generic.
Is there a way to have each handler function use a generic event type or a base event? Or am I destined for a wall given that arrays expect all the same types throughout?
1 Like
I suspect you will need to define a single type for all of your array elements. For example:
type event =
| PointerMoveEvent(Dom.element, Dom.pointerEvent => unit)
| PointerUpEvent(Dom.element, Dom.pointerEvent => unit)
| KeyUpEvent(Dom.element, Dom.keyboardEvent => unit)
let events = [
PointerMoveEvent(document, (_e: Dom.pointerEvent) => ()),
PointerUpEvent(document, (_e: Dom.pointerEvent) => ()),
KeyUpEvent(document, (_e: Dom.keyboardEvent) => ()),
]
1 Like
Thanks! That could work. Another option I found is
useEventHandlers([
(document, "pointermove", (e: Dom.pointerEvent) => ()),
(document, "pointerup", (e: Dom.pointerEvent) => ()),
])
useEventHandlers([
(document, "keyup", (e: Dom.keyboardEvent) => ()),
])
I wonder if a hook that takes a single tuple would be best after all?
As I started to think through the use case, I don’t think that abstraction is good enough. It makes a lot of assumptions on how the underlying useEffect should work, and with arrays requiring the same type I felt less confident in this approach. Taking a step back, I found a more flexible solution:
module Event = {
type eventListener<'a> = Dom.event_like<'a> => unit
@send
external addEventListener: (Dom.eventTarget_like<'a>, string, eventListener<'b>) => unit =
"addEventListener"
@send
external removeEventListener: (Dom.eventTarget_like<'a>, string, eventListener<'b>) => unit =
"removeEventListener"
module Listener = {
@ocaml.doc("
* Creates an event listener for a target DOM element. Returns a cleanup
* function to remove the listener
*
* @example
let remove = DomUtils.Listener.make(document, \"onclick\", Js.Console.log)
// later
remove()
")
let make = (target, eventName, eventListener) => {
target->addEventListener(eventName, eventListener)
() => {
target->removeEventListener(eventName, eventListener)
}
}
}
}
That way it can be composed with a useEffect in any number of ways:
@react.component
let make = () => {
React.useEffect(() => {
let listeners = [
DomUtils.Event.Listener.make(document, "pointermove", (e: Dom.pointerEvent) => ()),
DomUtils.Event.Listener.make(document, "pointerup", (e: Dom.pointerEvent) => ()),
DomUtils.Event.Listener.make(document, "keyup", (e: Dom.keyboardEvent) => ()),
]
Some(() => listeners->Array.forEach(remove => remove()))
})
{React.string("Listening")}
}