HandleMouseMove fired from mousemove event listener in React Js does not use updated state

Viewed 891

What works: My state variable "dragging" is set to true when the react onMouseDown event fires on the trigger. Also, "dragging" is set to false when mouseUp fires anywhere on the screen from a classic event listener. My mousemove listener fires the handleMouseMove function.

What does not work: handleMouseMove checks if dragging is true, if it is true, it should print "I'm Dragging" but it looks like it never becomes true for handleMouseMove. ????

How can I get it to work and print "I'm Dragging" if dragging is true?

Note: Debugging with a useEffect that fires every time dragging changes, confirms that in fact, it changes from true to false correctly whenever react onMouseDown and classic mouseup events fire.

export const useDrag = () => {

const [dragging, setDragging] = useState(false);

useEffect(() => {
  window.addEventListener("mousemove", handleMouseMove );
  window.addEventListener("mouseup", handleMouseUp );
    
  return () => {
      window.removeEventListener("mousemove", handleMouseMove );
      window.removeEventListener("mouseup", handleMouseUp);
    };
  }, []);

function handleMouseDown (e)  {
    e.stopPropagation();
    e.preventDefault();
    setDragging(true);
  };

function handleMouseUp(e){
    e.stopPropagation();
    e.preventDefault();
    setDragging(false);
  }

function handleMouseMove(e){
    if(dragging) console.log("I'm Dragging");
  }

const bindTrigger = {
    onMouseDown: handleMouseDown,
}

return [ bindTrigger ]
}
1 Answers

You bind the handlers on mount in the useEffect, but since the useEffect doesn't have any dependencies, it doesn't update the bound functions when they are re-created (which happens on each render). So for the handlers, dragging is always false.

You should only bind the handlers to the window when dragging is true. Move the move and up handlers into the useEffect(), and make it dependent on dragging. If dragging is false it would only call the cleanup function to remove the handlers from window.

const { useState, useEffect } = React;

const useDrag = () => {
  const [dragging, setDragging] = useState(false);

  useEffect(() => {
    if (!dragging) return;

    function handleMouseMove(e) {
      if (dragging) console.log("I'm Dragging");
    }

    function handleMouseUp(e) {
      e.stopPropagation();
      e.preventDefault();
      setDragging(false);
    }

    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("mouseup", handleMouseUp);

    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
      window.removeEventListener("mouseup", handleMouseUp);
    };
  }, [dragging]);
  
  function handleMouseDown(e) {
    e.stopPropagation();
    e.preventDefault();
    setDragging(true);
  };

  const bindTrigger = {
    onMouseDown: handleMouseDown,
  }

  return [bindTrigger];
}

const Demo = () => {
  const [bindTrigger] = useDrag();

  return (
    <div {...bindTrigger}>Drag me</div>
  );
}

ReactDOM.render(
  <Demo />,
  root
);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="root"></div>

Related