If you have not used
ref
's before, please take a look at theReact
: 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>;
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.
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>
);
}
}
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.
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 likedomRef
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 justref
. It uses the React 16forwardRef
API to pass the DOM node through theref
callback rather than the instance of the Component. Similarly, emotion v10+ theinnerRef
prop was deprecated in favorref
with aforwardRef
passthrough.
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>
);
}
}
Here is an example that shows off the learnings presented in this guide: https://codesandbox.io/s/v3p0q71qn5
@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.