17

I don't fully understand it but apparently it isn't recommended to use findDOMNode().

I'm trying to create drag and drop component but I'm not sure how I should access refs from the component variable. This is an example of what I currently have:

const cardTarget = {
    hover(props, monitor, component) {
        ...
        // Determine rectangle on screen
        const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
        ...
    }
}

Source

Edit

It might be caused by my component being both the drag and drop source and target as I can get it to work in this example but not this one.

Zach
  • 467
  • 1
  • 2
  • 18
joshhunt
  • 4,685
  • 2
  • 31
  • 56

3 Answers3

22

Assuming you're using es6 class syntax and the most recent version of React (15, at time of writing), you can attach a callback ref like Dan did in his example on the link you shared. From the docs:

When the ref attribute is used on an HTML element, the ref callback receives the underlying DOM element as its argument. For example, this code uses the ref callback to store a reference to a DOM node:

<h3
    className="widget"
    onMouseOver={ this.handleHover.bind( this ) }
    ref={node => this.node = node}
>

Then you can access the node just like we used to do with our old friends findDOMNode() or getDOMNode():

handleHover() {
  const rect = this.node.getBoundingClientRect(); // Your DOM node
  this.setState({ rect });
}

In action: https://jsfiddle.net/ftub8ro6/

Edit:

Because React DND does a bit of magic behind the scenes, we have to use their API to get at the decorated component. They provide getDecoratedComponentInstance() so you can get at the underlying component. Once you use that, you can get the component.node as expected:

hover(props, monitor, component) {
    const dragIndex = monitor.getItem().index;
    const hoverIndex = props.index;
    const rawComponent = component.getDecoratedComponentInstance();
    console.log( rawComponent.node.getBoundingClientRect() );
    ...

Here it is in action:

https://jsfiddle.net/h4w4btz9/2/

imjared
  • 15,060
  • 2
  • 42
  • 66
  • I got it working in [this example](https://jsfiddle.net/hx7eutm1/2/) but I still can't get it working in my live code. I wonder if it is because my element is both the drop target and the drop source. I'll do some more testing. – joshhunt Nov 14 '16 at 20:01
  • Yeah can't seem to get it working in [this example](https://jsfiddle.net/h4w4btz9/1/) (see line 25) where my component is the DnD target and source. – joshhunt Nov 14 '16 at 20:16
  • @joshhunt see update. I think it's now doing what you expect – imjared Nov 14 '16 at 20:58
  • Thanks for the explanation. ! – jackncoke Apr 10 '17 at 14:14
  • Unfortunately, callback refs are less cohesive and therefore less readable than grabbing the element with findDOMNode. Declaring new properties nested in the markup is problematic as a general pattern. – Ben Creasy Oct 17 '17 at 18:15
  • I found that if I just swap the order of the higher order functions (dragSource and dropTarget) I didn't have to use getDecoratedComponentInstance – Odin Thorsen Sep 20 '18 at 10:04
3

Better Solution

A better solution is to just wrap your draggable component with a div, define a ref on that and pass it to the draggable component, i.e.

<div key={key} ref={node => { this.node = node; }}>
  <MyComponent
    node={this.node}
  />
</div>

and MyComponent is wrapped in DragSource. Now you can just use

hover(props, monitor, component) {
  ...
  props.node && props.node.getBoundingClientRect();
  ...
}

(props.node && is just added to avoid to call getBoundingClientRect on an undefined object)

Alternative for findDOMNode

If you don't want to add a wrapping div, you could do the following. The reply of @imjared and the suggested solution here don't work (at least in react-dnd@2.3.0 and react@15.3.1).

The only working alternative for findDOMNode(component).getBoundingClientRect(); which does not use findDOMNode is:

hover(props, monitor, component) {
  ...
  component.decoratedComponentInstance._reactInternalInstance._renderedComponent._hostNode.getBoundingClientRect();
  ...
}

which is not very beautiful and dangerous because react could change this internal path in future versions!

Other (weaker) Alternative

Use monitor.getDifferenceFromInitialOffset(); which will not give you precise values, but is perhaps good enough in case you have a small dragSource. Then the returned value is pretty predictable with a small error margin depending on the size of your dragSource.

Community
  • 1
  • 1
Andru
  • 3,953
  • 3
  • 29
  • 42
0

React-DnD's API is super flexible—we can (ab)use this.

For example, React-DnD lets us determine what connectors are passed to the underlying component. Which means we can wrap them, too. :)

For example, let's override the target connector to store the node on the monitor. We will use a Symbol so we do not leak this little hack to the outside world.

const NODE = Symbol('Node')

function targetCollector(connect, monitor) {
  const connectDropTarget = connect.dropTarget()
  return {
    // Consumer does not have to know what we're doing ;)
    connectDropTarget: node => {
      monitor[NODE] = node
      connectDropTarget(node)
    }
  }
}

Now in your hover method, you can use

const node = monitor[NODE]
const hoverBoundingRect = node.getBoundingClientRect()

This approach piggybacks on React-DnD's flow and shields the outside world by using a Symbol.

Whether you're using this approach or the class-based this.node = node ref approach, you're relying on the underlying React node. I prefer this one because the consumer does not have to remember to manually use a ref other than the ones already required by React-DnD, and the consumer does not have to be a class component either.

Jeff
  • 11,384
  • 12
  • 77
  • 145