2

I have a component that currently uses the useDrag hook to connect to react-dnd. It works well, except for previews. I want to implement useDragLayer instead to see if it would help with my preview problems, as many online threads suggest.

This is my current (simplified) useDrag implementation:

const [{ isDragging }, connectDragSource, connectPreview] = useDrag({
  item,
  collect: monitor => ({
    isDragging: monitor.getItem()?.index === item.index,
  })
})

return (
  <Wrapper ref={connectPreview} isDragging={isDragging}>
    <DragHandle ref={connectDragSource} />
  </Wrapper> 
)

How do I use useDragLayer in this context, in a way that might help with my previews? The docs example makes little sense to me...

How do I connect my rendered components using useDragLayer api? useDragLayer doesn't return drag source and preview connector functions (like useDrag does on index 1 and 2 of the returned array), and its collect function doesn't provide a DragSourceConnector instance either. So what do I do with the hook/returned value after I call it?

Michal Kurz
  • 1,592
  • 13
  • 41

1 Answers1

11

I just resolved this and want to share it to help others :)

You will need to do couple of things for this to fully work.

  1. Disable the default preview behavior by adding the following useEffect
import { getEmptyImage } from "react-dnd-html5-backend";

const [{ isDragging }, drag, dragPreview] = useDrag(() => ({
  type: "BOX",
  collect: (monitor) => ({
    isDragging: monitor.isDragging(),
  }),
}));

useEffect(() => {
  dragPreview(getEmptyImage(), { captureDraggingState: true });
}, []);
  1. Create the custom default layer

export const CustomDragLayer = (props: {}) => {
  const {
    itemType,
    isDragging,
    initialCursorOffset,
    initialFileOffset,
    currentFileOffset,
  } = useDragLayer((monitor) => ({
    item: monitor.getItem(),
    itemType: monitor.getItemType(),
    initialCursorOffset: monitor.getInitialClientOffset(),
    initialFileOffset: monitor.getInitialSourceClientOffset(),
    currentFileOffset: monitor.getSourceClientOffset(),
    isDragging: monitor.isDragging(),
  }));

  if (!isDragging) {
    return null;
  }

  return (
    <div style={layerStyles}>
      <div
        style={getItemStyles(
          initialCursorOffset,
          initialFileOffset,
          currentFileOffset
        )}
      >
        <div>Your custom drag preview component logic here</div>
      </div>
    </div>
  );
};

const layerStyles: CSSProperties = {
  position: "fixed",
  pointerEvents: "none",
  zIndex: 100,
  left: 0,
  top: 0,
  width: "100%",
  height: "100%",
  border: "10px solid red",
};

function getItemStyles(
  initialCursorOffset: XYCoord | null,
  initialOffset: XYCoord | null,
  currentOffset: XYCoord | null
) {
  if (!initialOffset || !currentOffset || !initialCursorOffset) {
    return {
      display: "none",
    };
  }

  const x = initialCursorOffset?.x + (currentOffset.x - initialOffset.x);
  const y = initialCursorOffset?.y + (currentOffset.y - initialOffset.y);
  const transform = `translate(${x}px, ${y}px)`;

  return {
    transform,
    WebkitTransform: transform,
    background: "red",
    width: "200px",
  };
}

  1. Add the <CustomDragLayer /> to the top-level component

You will need to include the ref={drag} to the component you want to drag and remove the connectPreview ref completely.

Hopefully, this helps you.

KhaledMohamedP
  • 5,000
  • 3
  • 28
  • 26
  • Thank you. This is a lot more useful than the example on the react-dnd website. – DPA May 10 '23 at 15:23