Skip to content

How to replicate HTML5 drag preview in a custom drag layer? #428

@prashcr

Description

@prashcr

I started off my app using HTML5 backend for simplicity, and thought it would be easy to migrate to https://github.com/yahoo/react-dnd-touch-backend later when I needed mobile support.

I'm not looking for a 100% perfect replication, as long as what's being dragged looks mostly like it was before drag.

Previously, this was handled automatically by the HTML5 backend. But using touch backend, at least from what I can see in the examples, I have to re-render my entire component inside my custom drag layer.
When I try to render my component in my custom drag layer, I face the following two issues as a result

Warning: setState(...): Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state

My component receives props from a couple of other components higher in my tree, using alt's flux implementation. How can I render it with those same props? since my custom drag layer is not an ancestor of my draggable component, I don't know how to pass its current props to my custom drag layer for rendering.

Here is one of my draggable components:
As you can see, it recieves some props from its ancestors, such as onMove

import React from 'react';
import {DragSource, DropTarget} from 'react-dnd';
import ItemTypes from '../constants/itemTypes';

const noteSource = {
  beginDrag(props) {
    return {
      id: props.id
    };
  },
  isDragging(props, monitor) {
    return props.id === monitor.getItem().id;
  }
};

const noteTarget = {
  hover(targetProps, monitor) {
    const targetId = targetProps.id;
    const sourceProps = monitor.getItem();
    const sourceId = sourceProps.id;

    if (sourceId !== targetId) {
      targetProps.onMove({sourceId, targetId});
    }
  }
};

@DragSource(ItemTypes.NOTE, noteSource, (connect, monitor) => ({
  connectDragSource: connect.dragSource(),
  connectDragPreview: connect.dragPreview(),
  isDragging: monitor.isDragging() // map isDragging() state to isDragging prop
}))
@DropTarget(ItemTypes.NOTE, noteTarget, (connect) => ({
  connectDropTarget: connect.dropTarget()
}))
export default class Note extends React.Component {
  render() {
    const {connectDragSource, connectDropTarget, isDragging,
      id, onMove, editing, ...props} = this.props;
    // Pass through if we are editing
    const dragSource = editing ? a => a : connectDragSource;

    return dragSource(connectDropTarget(
      <li style={{
        opacity: isDragging ? 0 : 1
      }} {...this.props}>{this.props.children}</li>
    ));
  }
}

Here is my custom drag layer, near the top of my component hierarchy:

import React from 'react';
import ItemTypes from '../constants/itemTypes';
import Lane from './Lane';
import Note from './Note';
import {DragLayer} from 'react-dnd';

const layerStyles = {
  position: 'fixed',
  pointerEvents: 'none',
  zIndex: 100,
  left: 0,
  top: 0,
  width: '100%',
  height: '100%'
};

function getItemStyles (props) {
  const { initialOffset, currentOffset } = props;
  if (!initialOffset || !currentOffset) {
    return {
      display: 'none'
    };
  }

  // http://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/
  const { x, y } = currentOffset;
  var transform = `translate(${x}px, ${y}px)`;

  return {
    transform: transform,
    WebkitTransform: transform
  };
}

@DragLayer((monitor) => ({
  item: monitor.getItem(),
  itemType: monitor.getItemType(),
  initialOffset: monitor.getInitialSourceClientOffset(),
  currentOffset: monitor.getSourceClientOffset(),
  isDragging: monitor.isDragging()
}))
export default class CustomDragLayer extends React.Component {
  renderItem(type, item) {
    switch (type) {
      case ItemTypes.LANE:
        return null;
      case ItemTypes.NOTE:
        // How can I render my component as it is currently displayed?
        // I don't know how to pass its props, and this causes the setState warning
        // <Note /> 
        return null;
      default:
        return null;
    }
  }

  render () {
    const { item, itemType, isDragging } = this.props;

    if (!isDragging) {
      return false;
    }

    return <div style={layerStyles}>
      <div style={getItemStyles(this.props)}>
        {this.renderItem(itemType, item)}
      </div>
    </div>
  }
}

My entire code is here, it's a very small Kanban app, <10 components
https://github.com/prashcr/kanban-app

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions