Reputation: 175
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
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
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