4

I've created a simple sortable list with react-dnd using code similar to react-dnd's sortable example found here in codesandbox.

However, I'm having some difficulties trying to to conceptualize how I should tweak this example and utilize react-dnd's custom drag layer to customize the drag preview. Specifically, I want to change the background color of the component once I start dragging it. Nothing too complex.

Why do I need to use a custom drag layer? Because I can't style the drag preview using CSS due to limitations of browser API's and react-dnd's HTML5 Backend (which is what I'm using).

I haven't been able to find any examples using a custom drag layer within a sortable list so any help would be appreciated.

SMAKSS
  • 9,606
  • 3
  • 19
  • 34
Andrew Garrison
  • 182
  • 2
  • 11

2 Answers2

6

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.

Vočko
  • 2,478
  • 1
  • 23
  • 31
0

You can add css when hover on Card. Not sure this is what you need...

import Radium from "radium";
import "./style.css";
const style = {
  border: "1px dashed gray",
  padding: "0.5rem 1rem",
  marginBottom: ".5rem",
  backgroundColor: "white",
  cursor: "move",
  ":hover": {
    background: "purple"
  }
};
export default Radium(Card);

check here CodeSandBox

iamhuynq
  • 5,357
  • 1
  • 13
  • 36
  • Not quite what I want but closer. I tried changing the hover state to an "active" state and it got a little buggy. – Andrew Garrison Apr 06 '20 at 14:17
  • I also don't necessarily like the idea of adding Radium (or really any other library) to the mix. But thank you for the suggestion! – Andrew Garrison Apr 06 '20 at 14:24
  • i try another way with state [CodeSandBox](https://codesandbox.io/s/goofy-frog-ebf8b) – iamhuynq Apr 06 '20 at 14:26
  • I think using onMouseDown might be better for my use case. However, I'm having some troubles now removing the active style once the drop is completed. See this https://codesandbox.io/s/zen-noether-g59es – Andrew Garrison Apr 06 '20 at 15:08
  • 1
    change `onMouseUp` to `onMouseLeave`. I see it works – iamhuynq Apr 06 '20 at 15:14