For anyone still struggling with this, i found a solution without using the draggable library that works smoothly.
Create 4 state variables for your modal. Two for the Modal header, and two for the main Component. Create a ref for the header aswell.
const [headerLeft, setHeaderLeft] = useState(0);
const [left, setLeft] = useState('0');
const [headerTop, setHeaderTop] = useState(0);
const [top, setTop] = useState('0');
const modalHeader = useRef<HTMLDivElement>(null);
Using the ref you created for the header, add a listener on the modal header OnMouseDown. Then add a listener on the document to implement your dragging mechanism.
modalHeader.current?.addEventListener("mousedown", () => {
document.addEventListener("mousemove", onDrag);
});
Then create your dragging function. I've added an id on my modal header to be able to get the HTMLElement using getElementById. Then i'm calculating the new position based on top & left vars and update my state.
function onDrag(e) {
const modalHeaderElement = document.getElementById('modal-header');
if (modalHeaderElement) {
const getStyle = window.getComputedStyle(modalHeaderElement);
const leftVal = parseInt(getStyle.left);
const topVal = parseInt(getStyle.top);
setHeaderLeft(e.movementX + leftVal);
setHeaderTop(e.movementY + topVal);
setLeft(`${leftVal + e.movementX}px`);
setTop(`${topVal + e.movementY}px`);
}
}
Then all you need to do is update the style prop on the modal & modal header components
<Modal style={{ left: left, top: top }} >
<Modal.Header id='modal-header' ref={modalHeader} style={{ left: headerLeft, top: headerTop}}>A Header </Modal.Header>
<Modal.Body></Modal.Body>
</Modal>
Finally add another listener on the document for the mouseUp to stop dragging
document.addEventListener("mouseup", () => {
document.removeEventListener("mousemove", onDrag);
});
In my use-case, i've placed the addEventListeners and OnDrag function inside a useImperativeHandle hook because that's how i utilize my Modal (I'm wrapping it with a forwardRef). But i'm guessing you can do that inside a useEffect and work just as well.
Edit: You can also init the state variables to not keep your modal in last position that you dragged it.
Edit 2: Posting the code below to make it a custom hook and re-use it in your code. The idea is that the custom hook will return the state variables to update the coordinates of your modal. The input of the custom hook is the current of your modalHeader ref , the id you've provided in the Modal.Header element and the show state variable that shows or hides your modal.
export function useDraggable(modalHeader: HTMLDivElement | null, modalHeaderId: string, show: boolean) {
const [headerLeft, setHeaderLeft] = useState(0);
const [left, setLeft] = useState('0px');
const [headerTop, setHeaderTop] = useState(0);
const [top, setTop] = useState('0px');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onDrag = useCallback((e: any) => {
const modalHeaderElement = document.getElementById(modalHeaderId);
if (modalHeaderElement) {
const getStyle = window.getComputedStyle(modalHeaderElement);
const leftVal = parseInt(getStyle.left);
const topVal = parseInt(getStyle.top);
setHeaderLeft(_prevState => e.movementX + leftVal);
setHeaderTop(_prevState => e.movementY + topVal);
setLeft(_prevState => `${leftVal + e.movementX}px`);
setTop(_prevState => `${topVal + e.movementY}px`);
}
}, []);
const onMouseUp = useCallback(() => {
document.removeEventListener("mousemove", onDrag);
const modalHeaderElement = document.getElementById(modalHeaderId);
if (modalHeaderElement) {
modalHeaderElement.classList.remove("active");
}
}, []);
useEffect(() => {
const modalHeaderElement = document.getElementById(modalHeaderId);
if (modalHeaderElement && show) {
modalHeaderElement.addEventListener("mousedown", () => {
modalHeaderElement.classList.add("active");
document.addEventListener("mousemove", onDrag);
});
document.addEventListener("mouseup", onMouseUp);
}
if (!show) {
if (modalHeader) {
document.removeEventListener("mouseup", onMouseUp);
}
setLeft('0px');
setTop('0px');
setHeaderLeft(0);
setHeaderTop(0);
}
}, [modalHeaderId, modalHeader, show, onMouseUp, onDrag])
return {
headerTop,
headerLeft,
top,
left
};
}
import Draggable from 'react-draggable';
– Willard