How does react-dnd's connectDragPreview() work? How does react-dnd's connectDragPreview() work? reactjs reactjs

How does react-dnd's connectDragPreview() work?


Its seems you are using react-dnd-html5-backend. React-dnd-html5-backend provides connectDragSource: https://github.com/react-dnd/react-dnd-html5-backend/blob/85fc956c58a5d1a9fde2fca3c7fca9115a7c87df/src/HTML5Backend.js#L100

And react-dnd-html5-backend works only with html5 drag&drop events: https://github.com/react-dnd/react-dnd-html5-backend/blob/85fc956c58a5d1a9fde2fca3c7fca9115a7c87df/src/HTML5Backend.js#L65-L74

So, you can set up only an Image to you drag effect: https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/setDragImage

You can simply change your img.onload callback:

export const tokenCollector: DragSourceCollector = (connect, monitor) => {  return {    connectDragSource: connect.dragSource(),    connectDragPreview: connect.dragPreview(),    isDragging: monitor.isDragging()  };};class TokenClass extends React.Component<TokenProps> {  componentDidMount() {    const { connectDragPreview } = this.props;    const img = new Image();    img.src = 'base64-image';    img.onload = () => connectDragPreview(img);  }  render() {    const { connectDragSource, isDragging, children } = this.props;    return connectDragSource(      <div style={{ opacity: isDragging ? 0.5 : 1 }}>        {children}      </div>    );  }}const dragSource = DragSource(DropType.Token, tokenSpec, tokenCollector)(TokenClass);export { dragSource as Token };

In case you want to use custom markup, you can call connectDragPreview with getEmptyImage argument, and then, implement CustomDragLayer.

Your TokenClass:

import React, { Component } from "react";import { DragSource } from "react-dnd";import logo from "./logo.svg";import { getEmptyImage } from "react-dnd-html5-backend";export const tokenCollector = (connect, monitor) => {  return {    connectDragSource: connect.dragSource(),    connectDragPreview: connect.dragPreview(),    isDragging: monitor.isDragging()  };};class TokenClass extends React.Component {  componentDidMount() {    const { connectDragPreview } = this.props;    // Use empty image as a drag preview so browsers don't draw it    // and we can draw whatever we want on the custom drag layer instead.    connectDragPreview(getEmptyImage());  }  render() {    const { connectDragSource, isDragging, children } = this.props;    return connectDragSource(      <div style={{ opacity: isDragging ? 0.5 : 1, backgroundColor: "green" }}>        {children}      </div>    );  }}const tokenSpec = {  beginDrag() {    return {};  }};export default DragSource("DropType.Markup", tokenSpec, tokenCollector)(  TokenClass);

CustomDragLayer implementation:

import React, { Component } from "react";import { DragLayer } from "react-dnd";function getItemStyles(props) {  const { initialOffset, currentOffset } = props;  if (!initialOffset || !currentOffset) {    return {      display: "none"    };  }  let { x, y } = currentOffset;  const transform = `translate(${x}px, ${y}px)`;  return {    transform,    WebkitTransform: transform  };}class CustomDragLayer extends Component {  render() {    const isDragging = this.props.isDragging;    if (!isDragging) {      return null;    }    // You can specify acceptable type:    if (this.props.itemType !== "DropType.Markup") {      return null;    }    // The component will work only when dragging    return (      <div>        <div style={getItemStyles(this.props)}>Custom drag layer!</div>      </div>    );  }}function collect(monitor) {  return {    item: monitor.getItem(),    itemType: monitor.getItemType(),    initialOffset: monitor.getInitialSourceClientOffset(),    currentOffset: monitor.getSourceClientOffset(),    isDragging: monitor.isDragging()  };}export default DragLayer(collect)(CustomDragLayer); // eslint-disable-line new-cap

Also, I uploaded to github solution which works with both an image and a markup. You can upload them and run: yarn installyarn startSolution: https://github.com/xnimorz/stackoverflow-example/tree/dragAndDrop


Try changing your img.onload to use the img directly instead of wrapping it in a div:

img.onload = () => connectDragPreview(img);

As far as I can tell, this is the only real difference in your code from the examples.


import React from 'react';import { DragSource } from 'react-dnd';/* ... */function collect(connect, monitor) {  return {    connectDragSource: connect.dragSource(),    connectDragPreview: connect.dragPreview()  };}class ComponentWithCopyEffect {  render() {    const { connectDragSource } = this.props;    return connectDragSource(      <div>        This div shows a plus icon in some browsers.      </div>,      { dropEffect: 'copy' }    );  }});ComponentWithCopyEffect = DragSource(/* ... */)(ComponentWithCopyEffect);class ComponentWithHandle {  render() {    const { connectDragSource, connectDragPreview } = this.props;    return connectDragPreview(      <div>        This div is draggable by a handle!        {connectDragSource(          <div>drag me</div>        )}      </div>    );  }}ComponentWithHandle = DragSource(/* ... */)(ComponentWithHandle);class ComponentWithImagePreview {  componentDidMount() {    const { connectDragPreview } = this.props;    const img = new Image();    img.src = '/image.jpg';    img.onload = () => connectDragPreview(img);  }  render() {    const { connectDragSource } = this.props;    return connectDragSource(      <div>        This div shows an image when dragged!      </div>    );  }}ComponentWithImagePreview = DragSource(/* ... */)(ComponentWithImagePreview);

Source