Drag and drop with react-dnd and react-motion

Chunwei
AnyMind Group
Published in
4 min readSep 29, 2022

Product management systems such as Jira, Trello and Storify are greatly used by many organisations to manage their company products. The software is designed to help the users to manage all kinds of use cases, from requirements and test case management to agile software development.

In order to help users manage the tasks efficiently, normally the interface will come with drag and drop feature which allows users to arrange the tasks accordingly. To implement drag and drop into React application, there is a powerful library called React DnD which does an excellent job of embracing the declarative rendering paradigm of React and abstracting drag and drop away from the DOM.

Although React DnD helps to build a complex drag and drop interface, but it does not come with fantasy animation. Hereby there is another popular library called React Motion that can help provide a general solution for animation.

Below are the sample code that referring to sample:

Integrate react-dnd and react-motion

  1. First install react-dnd and react-motion

yarn add react-dnd react-dnd-html5-backend react-motion or npm install react-dnd react-dnd-html5-backend react-motion

2. Create a folder named DragAndDrop with subfiles index.js and Card.js inside /src

3. Modify the content inside /src/DragAndDrop/index.js as below:

import React, { useCallback, useState } from 'react'
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import { Motion, spring } from 'react-motion'
import Card from './Card'
const DragAndDrop = () => {
const [cards, setCards] = useState([
{ id: 1, title: 'Item Card One' },
{ id: 2, title: 'Item Card Two' },
{ id: 3, title: 'Item Card Three' },
{ id: 4, title: 'Item Card Four' },
])
const containerStyle = {
padding: 24,
width: '40%'
}
const moveItem = useCallback((dragIndex, hoverIndex) => {
let items = [...cards];
const dragCard = items[dragIndex];
items.splice(dragIndex, 1);
items.splice(hoverIndex, 0, dragCard);
setCards(items);
}, [cards]);
return <div style={containerStyle}>
<DndProvider backend={HTML5Backend}>
{cards.map((card, index) => {
const { id, title } = card
return (
<Motion
defaultStyle={{ opacity: 0, x: -200 }}
key={id}
// you can define your own styles and pass to the component
style={{ opacity: spring(1), seconds: (index + 1) * 0.05, x: spring(0), }}
>
{({ opacity, seconds, x }) =>
<Card
id={id}
index={index}
key={id}
style={{
opacity,
transform: `translateX(${x}px)`,
transition: `${seconds}s`
}}
title={title}
moveItem={moveItem}
/>
}
</Motion>
)
})}
</DndProvider>
</div>
}
export default DragAndDrop

4. Modify the content inside /src/DragAndDrop/Card.js as below:

import React, { useRef } from 'react';
import { useDrag, useDrop } from 'react-dnd'
const Card = ({ id, index, style, title, moveItem }) => {
const ref = useRef(null)
const cardStyle = {
backgroundColor: '#fff',
border: '1px solid #dedede',
borderRadius: 3,
boxShadow: '0 2px 1px rgba(110, 124, 137, 0.05), 0 0 2px rgba(110, 124, 137, 0.25)',
color: '#27313b',
cursor: 'move',
fontSize: 14,
fontWeight: 600,
marginBottom: 16,
padding: 8,
}
const [{ handlerId }, drop] = useDrop({
accept: 'card',
collect(monitor) {
return {
handlerId: monitor.getHandlerId(),
}
},
hover(item, monitor) {
if (!ref.current) {
return
}

const dragIndex = item.index
const hoverIndex = index

// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return
}
// Determine rectangle on screen
const hoverBoundingRect = ref.current?.getBoundingClientRect()
// Get vertical middle
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2

// Determine mouse position
const clientOffset = monitor.getClientOffset()
// Get pixels to the top
const hoverClientY = clientOffset.y - hoverBoundingRect.top
// Only perform the move when the mouse has crossed half of the items height
// When dragging downwards, only move when the cursor is below 50%
// When dragging upwards, only move when the cursor is above 50%
// Dragging downwards
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return
}
// Dragging upwards
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return
}
// Time to actually perform the action
moveItem(dragIndex, hoverIndex)
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
// to avoid expensive index searches.
item.index = hoverIndex
},
})
const [{ isDragging }, drag] = useDrag({
type: 'card',
item: () => {
return { id, index }
},
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
})
const opacity = isDragging ? 0 : 1
drag(drop(ref))
return (
<div data-handler-id={handlerId} ref={ref} style={{ ...cardStyle, ...style, opacity }}>
{title}
</div>
)
}
export default Card

5. Import DragAndDrop component into App.js

6. yarn start or npm run start to run your application

And finally you will see the above output in your application.

--

--