'React useRef Hook mousemove affects two or more child components at the same time
I have no experience trying to do an interactive interface (drag-dropping, resizing elements) and I'm trying to develop my own custom scheduler calendar because we need specific rules to be applied to the scheduled events that market components don't have.
I already have the calendar layout and with react-dnd I can drag and drop events to the calendar, but I'm having problems when resizing the <div> event duration. Everything works fine if my events are in different day columns, they can be resize and the others aren't affected. But when I have two events in the same day column, when resized one the others column events also resize and I haven't figure out how to stop that.
I first map in the scheduler my events and create a custom <Event /> component. Here's a simplified version of that:
events.map(e => {
return (
<div className="absolute">
<Event id={e.id} />
</div>
)
})
Visually it looks like this: Events calendar image
Inside the <Event /> component I define a useRef and the actions to resize the event.
export default Event({id}) {
const duration = useRef(null)
useOutsideAlerter(duration)
function useOutsideAlerter(ref) {
useEffect(() => {
let dragging = false
const resizableDur = ref.current
const handleMouseDown = (e) => {
if (e.target.getAttribute('name') === "handler-"+ index) {
resizableDur.current = e.offsetY;
dragging = true;
}
};
const handleMouseUp = (e) => {
dragging = false;
};
const handleMouseMove = (e) => {
if (dragging) {
// get offset
const eventSize = resizableDur.getBoundingClientRect().height
// Get y-coordinate of pointer relative to container
var pointerRelativeYpos = eventSize + (e.offsetY - resizableDur.current);
// Arbitrary minimum height set on event, otherwise its inner content will collapse to height of 0
var eventAminHeight = 29;
// Resize event
resizableDur.style.height = (Math.max(eventAminHeight, pointerRelativeYpos - 8)) + 'px';
const endSlot = document.elementsFromPoint(e.x, e.y)
} else {
return
}
};
// Bind the event listener
document.addEventListener('mousedown', handleMouseDown);
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('mousemove', handleMouseMove);
return () => {
// Unbind the event listener on clean up
document.removeEventListener('mousedown', handleMouseDown);
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('mousemove', handleMouseMove);
};
}, [duration])
}
}
return (
<div className='group relative'>
<div name="cushion" className={`flex bg-sky-200 bg-stripes bg-stripes-white w-[8.375rem] content-center py-1 border-2 border-transparent group-hover:border-sky-400 ${isDragging ? "border-sky-400 cursor-grab" : ""} rounded-md`}>
<div ref={drag} name="actualevent" className='container'>
<div id={id} ref={duration} className='grid grid-cols-12 visible bg-sky-300 my-4 mx-0 w-full h-[1.813rem] overflow-hidden pl-1 py-1 border-2 border-sky-300 rounded-md pointer-events-none'>
<div className='w-fit h-fit col-span-2'>
<StationLogo station={station} scaleImg="100%" />
</div>
<div className='col-span-10 flex justify-start h-fit'>
<div className='lowercase text-xs pt-0.5 pl-1 pr-0.5 grid grid-cols-1 max-w-sm'>
<p className='text-ellipsis overflow-hidden truncate'>{Empresa}</p>
<span className='text-white text-ellipsis overflow-hidden truncate'>{product}</span>
<span className='text-white text-ellipsis overflow-hidden truncate font-semibold'>{moment(start, "x").format("MMM DD HH:mm")}</span>
<span className='text-white text-ellipsis overflow-hidden truncate'>interv. {time} {timetype}</span>
</div>
</div>
</div>
</div>
</div>
<div className="absolute flex justify-center inset-x-0 bottom-0 pointer-events-none">
<div name="phandler" className="pointer-events-auto absolute top-1/2 -mt-6 py-3.5 px-2 hidden md:block cursor-ns-resize select-none touch-pan-y origin-[50%_50%_0%]" draggable="false">
<div name={"handler-" + index} className="w-10 h-1.5 bg-sky-500 opacity-0 group-hover:opacity-100 rounded-full" />
</div>
</div>
<label className='absolute flex z-20 justify-center -right-16 bottom-3 opacity-0 transition ease-in duration-300 group-hover:opacity-100'>
<div className='bg-gray-400 text-white px-2 pb-1 pt-0 rounded-md'>
<span className='font antialiased text-xs'>{ moment(timeDuration, "HH:mm").format("HH:mm")}</span>
</div>
<div className="absolute w-2 h-2 rotate-45 bg-gray-400 top-3 -left-1"></div>
</label>
</div>
)
I put a console.log everytime mousemove executes and it show the id of the ref element. If the element is in a column alone console show only the ref <Event /> that I'm resizing, but if there's two or more <Event /> components in the same day column the the console log shows the one that I'm resizing and every ref id in the column.
I wonder if what I'm doing is the correct approach. I also tried using document.getElementById(), but I had the more or less the same result, if I have two <Event /> components in the same column, the first one that I resize doesn't affect others, but when I resize a second one in the same column then both resize at the same time.
Can somebody please help me, I'm stuck in this part and doesn't know how to solve it. Also I don't know how to detect layer collisions to avoid overlapping events in the calendar.
Thanks for the help.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
