I did run into the same issue recently and have to agree that documentation for this bit is not existing and it gave me quite some grief to figure things out.
Create CustomDragLayer
I have used this example (which works well, it is just a little overwhelming when you are new to the library) and created a simple CustomDragLayer
component:
import { DragLayerMonitor, useDragLayer } from 'react-dnd'
const CustomDragLayer: React.FC = () => {
const {isDragging, currentOffset, item} = useDragLayer(
(monitor: DragLayerMonitor) => {
return {
isDragging: monitor.isDragging(),
currentOffset: monitor.getSourceClientOffset(),
item: monitor.getItem()
};
}
);
return isDragging && currentOffset
? <div style={{
// functional
transform: `translate(${currentOffset.x}px, ${currentOffset.y}px)`,
position: 'fixed',
top: 0,
left: 0,
pointerEvents: 'none',
// design only
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '150px',
height: '50px',
border: '1px solid red',
color: 'red'
}}>
Dragging {item.id}
</div>
: null;
};
export default CustomDragLayer;
It expects that your dragged item has a property called id
(as it displays it). Most of the style properties aren't important, you need to make sure you set the transform
, position
, top
and left
.
The coordinates are absolute towards the window, and thus the position: fixed
makes life easier, otherwise some extra calculations might be needed.
Due to this issue, you have to add pointerEvents: 'none'
as otherwise the hover/drop detection doesn't work properly in Firefox and I have also noticed some strange behavior in Chrome (couldn't drag item on element that has click handler attached to it). Adding this property seems to fix the issues.
Hide the original element
Now you have the custom dragged element being rendered, however, the original source element is being visible and moves with the mouse too. That makes the experience quite ugly. Here I have taken the approach from the example and added an useEffect hook with no depenedencies (use componentDidMount
in a class component) to my Draggable
object that utilizes an empty image rendering function from the html5 back-end and hides the original preview.
Last thing I have decided to do was to hide the source item while being dragged. I use visibility
style property that preserves the original place in the dom and makes it looking like the item is actually being moved. This also depends on the context and you might decide to handle that differently.
Here is the Draggable
code excerpt:
import { getEmptyImage } from 'react-dnd-html5-backend';
const Draggable: React.FC = () => {
// ...
const [{ isDragging }, drag, dragPreview] = useDrag(() => ({
// ...
}));
useEffect(() => {
dragPreview(getEmptyImage())
}, []);
return <div ref={drag} style={{ visibility: isDragging ? 'hidden' : 'inherit'}}>
{/* your draggable content here*/}
</div>;
};
export default Draggable;
Add CustomDragLayer to the dom
Finally, once you have all this, you need to mount your custom drag element somewhere in the dom tree within your dnd root component.
return <div ref={drop}>
<Draggable ref={drag} />
<CustomDragLayer />
</div>
Hope this helps with understanding how it works in basics.