reactjsreasonbucklescriptreason-reactbs-webapi

Having trouble getting event data from event listener in ReasonReact


I'm trying to implement dynamic column resizing on a table (like in Excel or Google Sheets).

In my render function I use the handle callback when the user clicks mouse down on my resize control:

 <div
     className="resizer"
     onMouseDown={self.handle(handleColumnResizeStart)}
 />

In the handler I want to add a new event listener for mousemove so that when the user is "dragging" we can draw something to indicate where the new column edge would end up.

Within the mousemove handler I was thinking I could send a reducer action that contained the mouse clientX coordinate to update the component state so that the render function could draw something while they are dragging.

let handleColumnResizeStart = (evt, self) => {
  /* this handler gets invoked when the mouse is moved */
  let handleMouseMove = evt => {

    Js.log(evt); /* in js land I can see that clientX is in that evt object */
    Js.log(ReactEvent.Mouse.clientX(evt)); /* but this creates type errors */


  };
  /* adds an event handler using the bs-webapi module */
  Webapi.Dom.EventTarget.addEventListener(
    "mousemove",
    handleMouseMove,
    document,
  );

};

When I try to use ReactEvent.Mouse.clientX(evt) to get the specific int value of clientX, I get this error:

  25 Webapi.Dom.EventTarget.removeEventListener(
  26 ┆   "mousemove",
  27 ┆   handleMouseMove,
  28 ┆   document,
  29 ┆ );

  This has type:
    ReactEvent.Mouse.t => unit
  But somewhere wanted:
    Dom.event => unit

  The incompatible parts:
    ReactEvent.Mouse.t (defined as
      ReactEvent.synthetic(ReactEvent.Mouse.tag))
    vs
    Dom.event (defined as Dom.event_like(Dom._baseClass))

>>>> Finish compiling(exit: 1)

My understanding of the type system is limited here and I'm not sure how to get the value of the mouse clientX coordinate into variable.


Solution

  • Although events received from React and from event handlers attached directly to the DOM may look similar, they are in fact different. React doesn't give you a raw DOM event, but a SyntheticEvent, and in Reason they've therefore been given different types, which is what the type error is informing you of. You can't use a Dom.event where a ReactEvent.Mouse.t is expected. In this case evt is a Dom.event, because it was acquired by attaching an event handler directly to the DOM using bs-webapi, and ReactEvent.Mouse.clientX of course expects a ReactEvent.Mouse.t.

    So instead of using ReactEvent.Mouse.clientX, you should use Webapi.Dom.MouseEvent.clientX.

    Unfortunately that still won't work, because Webapi.Dom.MouseEvent.clientX expects a Dom.mouseEvent, not a Dom.event, which is the supertype of all DOM event types and too general to be used with functions specific to mouse events. And this in turn is because Webapi.Dom.EventTarget.addEventLsitener isn't able to understand that "mousemove"means it is a mouse event. You should instead use Webapi.Dom.EventTarget.addMouseMoveEventListener, which does give you a Dom.mouseEvent.

    Note that the type error you get is more confusing than it needs to be, because it will infer the type and point to the error being somewhere other than where you perceive the error to originate. It's a good idea to annotate the types, at least when you get a type error you struggle to understand. That way the compiler won't infer the type to be something you don't expect it to be, and will contain the origin of the error.

    You might also want to use Webapi.Dom.Document instead of Webapi.Dom.EventTarget. Document inherits everything in EventTarget, but will both document and constrain the type you operate on.