React DND, adding item dimensions to dragSource

1.5k views Asked by At

I have some html elements that I want to add drag and drop functionality to. I am using the useDragLayer hook to create a drag preview for the elements. These elements get their width from a flex container, so naturally to have the drag preview accurate I need to tell it the dimensions of the item that gets dragged. To do so, I am passing the width and height of the element in the drag source like this:

const [{isDragging,/*size*/}, dragRef, previewRef] = useDrag({
        item: {
            type: LIST_ITEM_DRAG_TYPE,
            item,
            size: {
                width: ref.current?.getBoundingClientRect().width,
                height: ref.current?.getBoundingClientRect().height
            }
        } as DraggedListItem,
        collect: monitor => ({
            isDragging: monitor.isDragging(),
            // size: (monitor.getItem() as DraggedListItem)?.size
        })
    })

Here, ref is a ReactRef for the html element. Then, I retrieve the dimensions in the DragLayer like this:

const {isDragging, currentOffset, item} = useDragLayer(monitor => ({
        currentOffset: monitor.getSourceClientOffset(),
        initialOffset: monitor.getInitialSourceClientOffset(),
        item: monitor.getItem(),
        isDragging: monitor.isDragging()
    }) as DragLayerProps)

Now here comes the problem. I drag an item from one container to another. The preview works as expected. But if I pick the item to move it again, the drag preview gets 'undefined' as the value for width and height. If I stop the drag, by letting the item back to the container it's currently in, all the subsequent drags on that item work as expected, until I drop it to a different container where the weird behavior starts again.

Initially I thought there was a problem with the ref, that it doesn't get to be set after the drag ends and remains undefined. But it doesn't seem to be the case. After dragging the element to a container, ref gets set to the correct value.

I think it has something to do with React-DnD. As you can see in the useDrag hook I have commented out in the collect function return value a propery size, where I just retrieve the item from the monitor to get the size, just as I would do in the DragLayer. If I uncomment that property, the issue seems to go away. I tried naming the property different (so it's not some name overlap between what's in item and what's in collect) and it still works. I can't understand why this works, of if it is just a coincident that adding that line of code solves the problem, and the bug is somewhere else.

Does anyone have any idea about what is going on here? Thanks in advance!

1

There are 1 answers

0
VDN On

I had very similar problem. I think this is because useDrag is created before component render, and ref is assigned after render (or something like that :) ). So, in some cases, there is no ref yet (it is undefined), but we already want to get its width/height.

To solve this issue, I am not trying to get width and height 'directly' while creating the useDrag hook, but I am passing a method for getting it. So, when the component starts dragging, it has been already rendered, and so the ref is already set, and it is safe to get width/height out of that:

 const dragItem: IDragItem<T> = {
        item: _item,
        getSize: () => ({
            width: ref.current?.getBoundingClientRect().width ?? 0,
            height: ref.current?.getBoundingClientRect().height ?? 0
        })
    }

    const [{ isDragging }, drag, preview] = useDrag(() => ({
        type: 'CARD',
        item: dragItem,
        collect: (monitor) => ({
            isDragging: monitor.isDragging()
        })
    }))