Brimstone
Brimstone

Reputation: 175

How to remove event listener in React

I have a code that is supposed to add event listener on a parent component when element is clicked and remove it when the mouse is up but the removing of event listener is not working.

      const move = (e)=>{
 setPosition({top:e.clientY-50,left:e.clientX-65})

}

const handleMouseDown = (e) =>{
    backgroundRef.current.addEventListener('mousemove',(e)=>move(e))
}

const handleMouseUp = (e) =>{
    backgroundRef.current.removeEventListener('mousemove',(e)=>move(e))
}

return <div ref={iconRef} onMouseDown={handleMouseDown} onMouseUp={handleMouseUp} style={position} className='icon-container' >
    <img draggable='false' src={image} alt=''/>
    <p>{text}</p>
</div>

Upvotes: 0

Views: 118

Answers (2)

John Li
John Li

Reputation: 7447

I think for removeEventListener to work in this case, in addition to passing the reference equal move for it, the functions might also need to be saved by useCallback. Because when state position changes and the component re-renders, handleMouseUp could be recreated with a new copy of move already.

Example with reference equal move and useCallback:

const Child = ({ image, text, backgroundRef }) => {
  const [position, setPosition] = React.useState(null);

  const move = React.useCallback((e) => {
    setPosition({
      top: `${e.clientY - 50}px`,
      left: `${e.clientX - 65}px`,
    });
  }, []);

  const handleMouseDown = React.useCallback(() => {
    backgroundRef.current.addEventListener('mousemove', move);
  }, [move]);

  const handleMouseUp = React.useCallback(() => {
    backgroundRef.current.removeEventListener('mousemove', move);
  }, [move]);

  return (
    <div
      onMouseDown={handleMouseDown}
      onMouseUp={handleMouseUp}
      style={position}
      className="icon-container"
    >
      <img draggable="false" src={image} alt="" />
      <p>{text}</p>
    </div>
  );
};

const Parent = () => {
  const backgroundRef = React.useRef(null);
  return (
    <section ref={backgroundRef}>
      <Child
        text="👉 Drag this"
        image="https://picsum.photos/100"
        backgroundRef={backgroundRef}
      />
    </section>
  );
};


const App = () => {
  return (
    <div>
      <Parent />
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#root"));
section {
  position: relative;
  width: 200px;
  height: 200px;
  background-color: pink;
  display: flex;
  justify-content: center;
  align-items: center;
}

.icon-container {
  position: absolute;
  background-color: lightgreen;
  cursor: pointer;
}

.icon-container > * {
  pointer-events: none;
  user-select: none;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>

But alternatively, if the goal is to make the component follow the mouse move, it might not be necessary to host the event from the parent. The component could keep a active state to be toggled by clicks, which controls whether the dragging is active.

useCallback, addEventListener, and a ref from the parent can be omitted with this optional approach.

Example with active state:

const Child = ({ image, text }) => {
  const [active, setActive] = React.useState(false);
  const [position, setPosition] = React.useState(null);

  const move = (e) => {
    if (!active) return;
    setPosition({
      top: `${e.clientY - 50}px`,
      left: `${e.clientX - 65}px`,
    });
  };

  return (
    <div
      onMouseDown={() => setActive(true)}
      onMouseUp={() => setActive(false)}
      style={position}
      className="icon-container"
      onMouseMove={active ? move : null}
    >
      <img draggable="false" src={image} alt="" />
      <p>{text}</p>
    </div>
  );
};

const Parent = () => {
  return (
    <section>
      <Child text="👉 Drag this" image="https://picsum.photos/100" />
    </section>
  );
};

const App = () => {
  return (
    <div>
      <Parent />
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#root"));
section {
  position: relative;
  width: 200px;
  height: 200px;
  background-color: lightblue;
  display: flex;
  justify-content: center;
  align-items: center;
}

.icon-container {
  position: absolute;
  background-color: pink;
  cursor: pointer;
}

.icon-container > * {
  pointer-events: none;
  user-select: none;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>

Upvotes: 0

AbdallahAyed
AbdallahAyed

Reputation: 74

You need to pass the same function reference to both addEventListener and removeEventListener. You are currently passing a new anonymous function on each call.

const move = (e)=>{
 setPosition({top:e.clientY-50,left:e.clientX-65})

}

const handleMouseDown = (e) =>{
    backgroundRef.current.addEventListener('mousemove',move) // passing the same reference 
}

const handleMouseUp = (e) =>{
    backgroundRef.current.removeEventListener('mousemove',move) // passing the same reference 
}

Upvotes: 1

Related Questions