16
How to create a smooth draggable list with react in 10 mins
Welcome to another tutorial!!
Today, we are going to create a draggable list that you can use in your react applications with ease, using framer-motion. If you go to their examples page on codesandbox , you will see this implemented. In case you want to know how you can do it yourself, read on.
To start, if you have a list of items in a container or div that you want to have your users reorder by just dragging one item over another to swap positions, it's not a hustle since you are in the right place. This is what we are going to build in a few minutes.

Now the code, my favorite part!!
import React from 'react';
import './style.css';
const List = [1, 2, 3, 4, 5, 6];
export default function App() {
return (
<div className="container">
{List.map((n, index) => (
<Item key={index} name={n} />
))}
</div>
);
}
function Item({ name }) {
return <div>{name}</div>;
}
This is a normal react app where you are exporting the main component called App. We have another component below that will be the item in the main we will be dragging. A list that we map to produce our six items in the main app.
For now, our item component is just normal div with no properties to make it interactive, we add the framer motion package since that is what we will be using.
yarn add framer-motion //or
npm i framer-motion
When its done doing it magic, we import it into our file and get the motion element from 'framer-motion'. Then change our normal div in the Item component to motion.div, so we have properties we can use to drag and reorder the item when rendered.
Next, we will have to add the properties we want to use;
function Item({ name }) {
return <motion.div drag="y">{name}</motion.div>;
}
This is what the code looks like now. If you dragged the element, you will realise it goes outside our container that the list rendered in. We don't want that, so we need to add constraints to make sure users can only drag within the container.
What usePositionReorder hook does is reorder our list that is rendered when we drag an item from one position to the other and also updates the position of the item given its new position after the drag event. Use measure position uses the ref of the item to determine its position from where it was dragged from and where it has been placed and its index in the list. This is what it uses to send the data to updatePosition from the usePositionReOrder hook. So it will take data from the item being dragged such as its dragoffset vertically, since we are only dragging along the y axis. This will help the hook swap the item that we are changing positions with the dragged item.
When the item is being dragged, aside from letting the component itself know that it is in a dragging state, we have to let our usePositionReOrder hook know, using another property which is the onViewBoxUpdate prop. This property is a callback that will fire every time the viewport is updated due to a drag activity. And it sends the delta of the item and the index to updateOrder from the usePositionReorder hook. The list is reordered and sent back to us in the orderedList and a new list is rendered into our view.
Our code finally looks like this
import React from 'react';
import './style.css';
import { motion } from 'framer-motion';
import { usePositionReorder } from './usePositionReorder';
import { useMeasurePosition } from './useMeasurePosition';
const List = ["Item One", "Item Two", "Item Three", "Item Four"];
export default function App() {
const [updatedList, updatePosition, updateOrder] = usePositionReorder(List);
return (
<ul className="container">
{updatedList.map((name, index) => (
<Item
key={name}
ind={index}
updateOrder={updateOrder}
updatePosition={updatePosition}
name={name}
/>
))}
</ul>
);
}
function Item({ name, updateOrder, updatePosition, ind }) {
const [isdragged, setIsDragged] = React.useState(false);
const itemRef = useMeasurePosition(pos => updatePosition(ind, pos));
return (
<li>
<motion.div
style={{
zIndex: isdragged ? 2 : 1,
height: name.length * 10
}}
dragConstraints={{
top: 0,
bottom: 0
}}
dragElastic={1}
layout
ref={itemRef}
onDragStart={() => setIsDragged(true)}
onDragEnd={() => setIsDragged(false)}
animate={{
scale: isdragged ? 1.05 : 1
}}
onViewportBoxUpdate={(_, delta) => {
isdragged && updateOrder(ind, delta.y.translate);
}}
drag="y">
{name}
</motion.div>
</li>
);
}
When clicking to drag the items in the list, you will realize they are a bit hard to drag from one end to the other. We can add the dragElastic property of 1 to make it smoother.
Till next time..
16