React Drag and Drop: An Easier Tutorial
Enabling HTML5 Drag and Drop in React is not as hard at it first appears.

I wrote this tutorial because I found it surprisingly hard to learn React DnD from the official overview and documentation. Hopefully, you find this example-driven tutorial helpful.
note: When I started this tutorial (days ago), React DnD was v2.6.0. Today it is v3.0.2. I left the project with v2.6.0 as I could not get the example to work in v3.0.2 (my guess is that the documentation had not caught up to the new version). I assume that the broad concepts are the same in the new version.
The full working example is available for download.
HTML5 Drag and Drop
It is helpful to first understand that modern browsers (including IE 11) already support HTML5 Drag and Drop. As you can see from example code, it is fairly simple:
- Create source elements
- Create target elements
- On the drag, set the payload of the event (same event is carried through to the drop)
- On the drop, get the payload of the event; and do something with it
The browser itself takes care of creating and moving the dragable image.
React Drag and Drop
The React Drag and Drop (DnD) library, provides a React-friendly layer on top of HTML5 Drag and Drop (when using the complimentary library react-dnd-html5-backend).
In much the same way as HTML5 Drag and Drop, with React DnD you:
- Create source components
- Create target components
- On the drag, set the payload of the event (same event is carried through to the drop)
- On the drop, update the event payload with target information
- On the drop, get the combined payload of the event; and do something with it
The Example

The key features of the working example are:
- The colored circles are the source components
- The gray shapes are the target components
- On drag, the source component is dimmed (made semi-transparent)
- On drag, the target components are highlighted (made black)
- The starting (on drag) payload of the event is the color string (one of red, green, or blue)
- On drop, a shape string (one of circle or square) is added to the payload
- On drop, a list of drops is appended to using the payload information
Setup
Having installed the npm libraries, react-dnd and react-dnd-html5-backend, you begin by wrapping a top-level component with the DragDropContext(HTML5Backend) higher order component (HOC); simply initializes the library on it and its descendants.
src/Board/index.jsx
...
export default DragDropContext(HTML5Backend)(Board);
The wrapped Board component is a plain stateful React component; providing a state consisting of drops (an array of drops consisting of the color and shape properties), a function, handleDrop (that adds to drops), and renders the sources, targets, and a listing of drops.
Sources and targets are tagged with strings that enables one to determine which sources can be dropped on which targets; in this example there is only one such string and thus all sources can be dropped on all targets.
src/Board/itemTypes.js
export const ITEM = 'ITEM';
Target
The first part of creating the target components is to create a stateless functional component (Target) supplied with the following properties:
- connectDropTarget: A function that is used to setup the target DOM node
- highlighted: A boolean that highlights (colors black) the component as a target when the source is dragged
- shape: The shape (string) of the target
src/Board/Target.js
...const Target = ({ connectDropTarget, highlighted, shape }) => (
connectDropTarget(
<div
className={`board__targets__target board__targets__target--${shape}`}
style={{ backgroundColor: highlighted ? 'black' : 'gray' }}
/>
)
);Target.propTypes = {
connectDropTarget: PropTypes.func.isRequired,
highlighted: PropTypes.bool.isRequired,
shape: PropTypes.string.isRequired,
}
...
The second part (in the same file in this example) wraps the Target component with a HOC returned from DropTarget; the parameters of which are:
- ITEM: The string that tags the target; again used to match sources and targets
- target: Defines the target portion of the event payload, e.g. the shape of the target.
- collect: Specifies the drag and drop properties (e.g., connectDropTarget and highlighted) supplied to the Target component. The remaining properties (shape) automatically pass through to the Target component.
src/Board/Target.js
...
const target = {
drop(props) {
const { shape } = props;
return ({
shape,
});
}
}const collect = (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
highlighted: monitor.canDrop(),
});export default DropTarget(ITEM, target, collect)(Target);
Source
Likewise, the first part of creating the source components is to create a stateless functional component (Source) supplied with the following properties:
- color: The color (string) of the source
- connectDragSource: A function that is used to setup the source DOM node
- isDragging: A boolean indicating that the source component has been dragged
src/Board/Source.js
...
const Source = ({ color, connectDragSource, isDragging }) => (
connectDragSource(
<div
className="board__sources__source"
style={{
backgroundColor: color,
opacity: isDragging ? 0.25 : 1,
}}
/>
)
);Source.propTypes = {
color: PropTypes.string.isRequired,
connectDragSource: PropTypes.func.isRequired,
isDragging: PropTypes.bool.isRequired,
}
...
The second part (in the same file in this example) wraps the Source component with a HOC returned from DragSource; the parameters of which are:
- ITEM: The string that tags the target; again used to match sources and targets
- source: Defines the source portion of the event payload, e.g. the color of the target with the beginDrag method. Also defines the endDrag method; called when a source is dropped onto a target which in-turn calls the onDrop property supplied from the parent (Board).
- collect: Specifies the drag and drop properties (e.g., connectDragSource and isDragging) supplied to the Source component. The remaining properties (color) automatically pass through to the Source component.
src/Board/Source.js
...
const source = {
beginDrag(props) {
const { color } = props;
return ({
color,
});
},
endDrag(props, monitor) {
if (!monitor.didDrop()) {
return;
}
const { onDrop } = props;
const { color } = monitor.getItem();
const { shape } = monitor.getDropResult();
onDrop(color, shape);
},
};
const collect = (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
});
export default DragSource(ITEM, source, collect)(Source);
Conclusion
That is it; we have illustrated all the key parts of implementing React Dnd.
There are several additional features that one can implement, e.g., using monitor.isOver() in the target collect parameter to focus the attention on a target when the source is over it. But I will leave it for you to learn about them from the documentation.
✉️ Subscribe to CodeBurst’s once-weekly Email Blast, 🐦 Follow CodeBurst on Twitter, view 🗺️ The 2018 Web Developer Roadmap, and 🕸️ Learn Full Stack Web Development.