Skip to content

Latest commit

 

History

History
222 lines (186 loc) · 6.29 KB

using-inner-ref.md

File metadata and controls

222 lines (186 loc) · 6.29 KB

Using innerRef

If you have not used ref's before, please take a look at the React: Refs and the DOM guide on their documentation website.

Our <Draggable /> and <Droppable /> components both require a HTMLElement to be provided to them. This is done using the innerRef property on the DraggableProvided and DroppableProvided objects.

<Draggable draggableId="draggable-1" index={0}>
  {(provided, snapshot) => (
    <div
+      ref={provided.innerRef}
      {...provided.draggableProps}
      {...provided.dragHandleProps}
    >
      <h4>My draggable</h4>
    </div>
  )}
</Draggable>;
<Droppable droppableId="droppable-1">
  {(provided, snapshot) => (
    <div
+     ref={provided.innerRef}
      {...provided.droppableProps}
    >
      <h2>I am a droppable!</h2>
      {provided.placeholder}
    </div>
  )}
</Droppable>;

Not all refs are created equal

Confusion can arise because of how the ref callback works in React.

On a Component such as <Person /> the ref callback will return the instance of the Person component.

On a ReactElement such as <div /> the ref callback will return the HTMLElement that the ReactElement is tied to.

See on codesandbox.io

class Person extends React.Component {
  state = {
    sayHello: false,
  };
  sayHello() {
    this.setState({
      sayHello: true,
    });
  }
  render() {
    if (this.state.sayHello) {
      return <div {...this.props}>Hello</div>;
    }

    return <div {...this.props}>'I am a person, I think..'</div>;
  }
}

class App extends React.Component {
  setPersonRef = (ref) => {
    this.personRef = ref;

    // When the ref changes it will firstly be set to null
    if (this.personRef) {
      // personRef is an instance of the Person class
      this.personRef.sayHello();
    }
  };
  setDivRef = (ref) => {
    this.divRef = ref;

    if (this.divRef) {
      // div ref is a HTMLElement
      this.divRef.style.backgroundColor = 'lightgreen';
    }
  };
  render() {
    return (
      <React.Fragment>
        <Person ref={this.setPersonRef} />
        <div ref={this.setDivRef}>hi there</div>
      </React.Fragment>
    );
  }
}

A common error 🐞

Take a look at this example:

<Draggable draggableId="draggable-1" index={0}>
  {(provided, snapshot) => (
    <Person
      ref={provided.innerRef}
      {...provided.draggableProps}
      {...provided.dragHandleProps}
    />
  )}
</Draggable>

While it looks correct, it will cause your application to explode 💥!

This is because @hello-pangea/dnd expects the provided.innerRef function for a <Draggable /> and a <Droppable /> to be called with the DOM node of the component, and not the instance of the class. In this example we are calling provided.innerRef with an instance of Person and not the underlying DOM node.

Exposing a DOM ref from your Component 🤩

A simple way to expose the HTMLElement of your component is to create your own innerRef prop:

class Person extends React.Component {
  render() {
    return (
      <div {...this.props} ref={this.props.innerRef}>
        I am a person, I think..
      </div>
    );
  }
}

Note, the name innerRef is just a convention. You could call it whatever you want for your component. Something like domRef is fine.

You can then correctly supply the DOM node to a <Draggable /> or <Droppable />

<Draggable draggableId="draggable-1" index={0}>
  {(provided, snapshot) => (
    <Person
-      ref={provided.innerRef}
+      innerRef={provided.innerRef}
      {...provided.draggableProps}
      {...provided.dragHandleProps}
    >
      <h4>My draggable</h4>
    </Person>
  )}
</Draggable>

Note, if you are using styled-components v4 the innerRef prop was replaced with just ref. It uses the React 16 forwardRef API to pass the DOM node through the ref callback rather than the instance of the Component. Similarly, emotion v10+ the innerRef prop was deprecated in favor ref with a forwardRef passthrough.

⚠️ This approach will cause a React warning as we are spreading all of the props of the component onto the DOM node. {...this.props} This includes the innerRef prop which React does not like you adding to an element. So you can set things up like this:

class Person extends React.Component {
  render() {
-    return (
-      <div {...this.props} ref={this.props.innerRef}>
-        I am a person, I think..
-      </div>
-    );
  }
}
class Person extends React.Component {
  render() {
+    const { provided, innerRef } = this.props;
+    return (
+      <div
+        {...provided.draggableProps}
+        {...provided.dragHandleProps}
+        ref={innerRef}
+      >
+        I am a person, I think..
+      </div>
+    );
  }
}

<Draggable draggableId="draggable-1" index={0}>
  {(provided, snapshot) => (
    <Person
      innerRef={provided.innerRef}
-      {...provided.draggableProps}
-      {...provided.dragHandleProps}
+      provided={provided}
    />
  )}
</Draggable>

If you also need to use the HTMLElement within your Component you can have a more powerful ref setting approach:

class Person extends React.Component {
  setRef = (ref) => {
    // keep a reference to the dom ref as an instance property
    this.ref = ref;
    // give the dom ref to @hello-pangea/dnd
    this.props.innerRef(ref);
  };
  render() {
    const { provided, innerRef } = this.props;
    return (
      <div
        {...provided.draggableProps}
        {...provided.dragHandleProps}
        ref={this.setRef}
      >
        I am a person, I think..
      </div>
    );
  }
}

Putting it all together

Here is an example that shows off the learnings presented in this guide: https://codesandbox.io/s/v3p0q71qn5

A note on SVG's

@hello-pangea/dnd does not support the dragging of <svg> elements. Wrap your <svg> in a HTMLElement such as <span> or <div> for great accessibility and cross browser support. See our dragging SVGs guide for more information.

← Back to documentation