Confused by context updating in handleOnMouseDown()

I can share more code if needed but the component is pretty large and I don't think all the context is needed for this. I have a component for a drag and drop canvas that when clicked should set the starting mouse position and then calculates it's position based on the difference between the current mouse position and the starting mouse position. The problem is that the starting mouse position gets set multiple times even though I only want it set once. Does anyone know why this is happening?
const Draggable: VoidComponent<{ id: string }> = (props) => {
const dragContext = useDragContext();

const handleMouseUp = () => {
document.removeEventListener("mousemove", dragContext.handleDrag);
document.removeEventListener("mouseup", handleMouseUp);
};

const handleMouseDown: JSX.EventHandler<HTMLDivElement, MouseEvent> = (e) => {
e.preventDefault();
if (!dragContext.state.selectedElementIds.includes(props.id)) {
dragContext.selectElement(props.id);
} else {
dragContext.unSelectElement(props.id);
}

dragContext.setDragStartMousePosition({ x: e.clientX, y: e.clientY });
dragContext.setDragStartPositions();

document.addEventListener("mousemove", dragContext.handleDrag);
document.addEventListener("mouseup", handleMouseUp);
};

return (
<div
style={{
top: `${dragContext.state.elements[props.id].position.y}px`,
left: `${dragContext.state.elements[props.id].position.x}px`,
}}
classList={{
"fixed h-40 w-40 rounded bg-blue3": true,
"border-4 border-orange6":
!!dragContext.state.selectedElementIds.includes(props.id),
}}
onMouseDown={handleMouseDown}
/>
);
};
const Draggable: VoidComponent<{ id: string }> = (props) => {
const dragContext = useDragContext();

const handleMouseUp = () => {
document.removeEventListener("mousemove", dragContext.handleDrag);
document.removeEventListener("mouseup", handleMouseUp);
};

const handleMouseDown: JSX.EventHandler<HTMLDivElement, MouseEvent> = (e) => {
e.preventDefault();
if (!dragContext.state.selectedElementIds.includes(props.id)) {
dragContext.selectElement(props.id);
} else {
dragContext.unSelectElement(props.id);
}

dragContext.setDragStartMousePosition({ x: e.clientX, y: e.clientY });
dragContext.setDragStartPositions();

document.addEventListener("mousemove", dragContext.handleDrag);
document.addEventListener("mouseup", handleMouseUp);
};

return (
<div
style={{
top: `${dragContext.state.elements[props.id].position.y}px`,
left: `${dragContext.state.elements[props.id].position.x}px`,
}}
classList={{
"fixed h-40 w-40 rounded bg-blue3": true,
"border-4 border-orange6":
!!dragContext.state.selectedElementIds.includes(props.id),
}}
onMouseDown={handleMouseDown}
/>
);
};
15 Replies
jesseb34r
jesseb34rOP2y ago
// function from dragContext
handleDrag: (e: MouseEvent) => {
state.selectedElementIds.forEach((id) => {
setState("elements", id, "position", {
x:
state.elements[id].dragStartPosition.x +
(e.clientX - state.dragStartMousePosition.x),
y:
state.elements[id].dragStartPosition.y +
(e.clientY - state.dragStartMousePosition.y),
});
});
};
// function from dragContext
handleDrag: (e: MouseEvent) => {
state.selectedElementIds.forEach((id) => {
setState("elements", id, "position", {
x:
state.elements[id].dragStartPosition.x +
(e.clientX - state.dragStartMousePosition.x),
y:
state.elements[id].dragStartPosition.y +
(e.clientY - state.dragStartMousePosition.y),
});
});
};
@._rb
REEEEE
REEEEE2y ago
does the function handleMouseDown get called multiple times? The issue is that you're adding the elements current position with the change in position meaning it becomes exponential. drag > now_position + change > drag > now_position + change the second now_position is new position set by the last drag event ah I misread Could be the setDragStartPositions function
jesseb34r
jesseb34rOP2y ago
Give me like 20 and I’ll push the code to GitHub Walking home right now oops, forgot about this, had a busy lunch break code is a bit dirty but here it is
jesseb34r
jesseb34rOP2y ago
GitHub
swingout-jd/test2.tsx at main · jesseb34r/swingout-jd
Contribute to jesseb34r/swingout-jd development by creating an account on GitHub.
jesseb34r
jesseb34rOP2y ago
@._rb
REEEEE
REEEEE2y ago
I think you just need to spread the position into a new object for start Position {... state.elements[id].position} Otherwise it references the position object in the store
jesseb34r
jesseb34rOP2y ago
ooohhhhh yeah that could do it hmm, still not doing it something is happening where things are not setting static values the dragStarts are changing and I don't know why It's something with the setState function in the setDragStartPositions function
REEEEE
REEEEE2y ago
You could also try to json parse and stringify the position But don't think that would change anything
jesseb34r
jesseb34rOP2y ago
I got it working
state.selectedElementIds.forEach((id) => {
// setState("elements", id, "dragStartPosition", {
// ...state.elements[id].position,
// });
setState(
produce((currentState) => {
currentState.elements[id].dragStartPosition = {
...currentState.elements[id].position,
};
})
);
});
state.selectedElementIds.forEach((id) => {
// setState("elements", id, "dragStartPosition", {
// ...state.elements[id].position,
// });
setState(
produce((currentState) => {
currentState.elements[id].dragStartPosition = {
...currentState.elements[id].position,
};
})
);
});
but I don't get it why does produce work but normal setState doesn't
REEEEE
REEEEE2y ago
Hmm not too sure I think it would have to do with the object referencing the store
jesseb34r
jesseb34rOP2y ago
yeah it's gotta be that. because the produce method references a copy of the previous state of the store, it keeps that value whereas even with the spread the other way must reference the live value of the position weird
setState("elements", id, (prev) => {
prev.dragStartPosition = prev.position;
return prev;
});
setState("elements", id, (prev) => {
prev.dragStartPosition = prev.position;
return prev;
});
^this doesn't work
setState("elements", id, (prev) => {
prev.dragStartPosition = { ...prev.position };
return prev;
});
setState("elements", id, (prev) => {
prev.dragStartPosition = { ...prev.position };
return prev;
});
^this does
Otonashi
Otonashi2y ago
produce gives you a proxy that tracks all changes made to it, which updates the underlying store in a way that prompts listeners to rerun in this case it's probably because of the merging behaviour i'd need to look at the code more but setState("elements", id, "dragStartPosition", {...}) will set x and y rather than changing the position object since it is an object if you want to replace the object then you would have to use setState("elements", id, { dragStartPosition: {...} }) since the store setter shallow merges non-array objects yeah that seems about right honestly the problem seems to me to be that position and dragStartPosition are the same object since they're both instantiated with initialPosition so until you replace either one with a new object any changes made to one will be made to the other and the initial code you had doesn't do that the produce variant does, and the one that uses a callback and spread does (since it's mostly equivalent to doing setState("elements", id, { dragStartPosition: {...} })) all in all i would probably not change anything except
- position: initialPosition,
- dragStartPosition: initialPosition,
+ position: {...initialPosition},
+ dragStartPosition: {...initialPosition},
- position: initialPosition,
- dragStartPosition: initialPosition,
+ position: {...initialPosition},
+ dragStartPosition: {...initialPosition},
which should solve your problems at the source though replacing the position objects every time is also possibly worth doing, since there might be some code that only listens to .position and not .position.[x|y]
jesseb34r
jesseb34rOP2y ago
i totally didn't catch this. let me try that out that was totally it dang
jesseb34r
jesseb34rOP2y ago
@._rb got it all working with collision detection and everything 🙂 https://swingout-jd.vercel.app/drag
SwingOut
Generated by create-jd-app
REEEEE
REEEEE2y ago
Nice!
Want results from more Discord servers?
Add your server