React Drag and Drop Sortable List
The project files are available here and the live demo is hosted on codesandbox:
A bit about the project:
In this project, I will be implementing react-beautiful-dnd into a sample project, we’ll display a list of dummy comments that we generated using the mockaroo.com.
Packages I have used:
Spoiler, we only need react-beautiful-dnd for the functionality, the rest of the packages I’ve used are just so I can focus more on the functionality and less on styling the application.
- https://www.npmjs.com/package/react-beautiful-dnd
- https://www.npmjs.com/package/node-sass
- https://www.npmjs.com/package/react-icons
- https://www.npmjs.com/package/bootstrap
Generating Dummy Data:
So, I generated dummy data using https://mockaroo.com/, attached below is the snapshot of my dummy data structure.
You can access the generated data here: https://github.com/arbaz52/react-dnd-sortable-list/blob/main/src/MOCK_DATA.json
Now that we have the data, let’s start working with the application.
File Structure:
Important note: I’ve used reference forwarding in this project.
Now let’s dive into the Components, the first one we’ll take a look at is the <Comments/> Component, because it is the simplest one.
import React, { forwardRef } from "react";// as react-beautiful-dnd const Comments = forwardRef(({ children, ...props }, ref) => {return (<ul ref={ref} className="comments">{children}</ul>);});export default Comments;
We are going to wrap this component with <Droppable/> component and this <Droppable/> requires a reference to the root html element inside of it. We’ll forward the reference passed to <Comments/> component and we’ll assign the ul tag/element (root element) this reference. This way, <Droppable/> component will have reference to root html element inside of it.
If this is not making any sense, do not worry, it will make sense, I promise you this. ❤
Next is the <Comment/> Component, it will render the comment with a nice icon that you can use to drag the comment.
import React, { forwardRef } from "react";import { GrDrag } from "react-icons/gr";//for formatting dateimport moment from "moment";
const Comment = forwardRef(({ comment, date, id, dragHandleProps, snapshot, ...props }, ref) => {return (<liref={ref}{...props}className={"comment card p-2 my-2 " + (snapshot.isDragging ? "hovering" : "")}><div className="d-flex align-items-top"><span {...dragHandleProps}><GrDrag /></span><small className="ml-2 text-dark">{comment}</small></div><div className="text-muted text-right"><small>{moment(date).format("DD/mm/yyyy")}</small></div></li>);});export default Comment;
Okay, so big code. Let’s get into it.
Just like the previous <Comments/> component, we’ll be wrapping <Comment/> with <Draggable/> component, this will make this component draggable. Keep this in mind, now let me explain each part.
Props:
- comment: (the text content of the comment)
- date: (date in ISO format, at which this comment was posted)
- dragHandleProps: (any html elment that we need to use to drag this component around will have these props)
- snapshot: (current snapshot of the drag, we’ll use this information to assign a class for now, but you can what you want with it)
- …props: (rest of the props passed down to this component, these will include the props sent by the <Draggable/> component that we’ll need to attach to this li element (root element)
We’re forwarding ref for the similar reason we were forwarding ref for <Comments/> component.
Integrating the components:
Now let’s mix these together, take a look at src/App.js
This is a big file, so we’ll only look at the important bits that need explanation, rest you can look up at the project’s repo.
Let’s take a look at what we’re rendering
return (...{/* rendering comments */}<DragDropContext onDragEnd={dragEnded}>
Every <Droppable/> component should have a unique droppableId attribute, we use this to identify the source and destination before/after a drag/drop event.
<Droppable droppableId="comments-wrapper">
<Droppable/> and <Draggable/> expect their children to be a function that is passed two parameters (provided and snapshot) so they can pass in additional functionality to their children (returned by the function) using these parameters.
{(provided, snapshot) => (
Here, you can see, the <Droppable/> requires reference (provided.innerRef) of the root html element inside of it, so as discussed before, the <Comments/> component forwards this ref object to it’s root html-element it renders, this way, the <Droppable/> component has a reference to said element.
<Comments ref={provided.innerRef} {...provided.droppableProps}>{comments.map((_comment, index) => {return (
Every <Draggable/> component should have a unique draggableId attribute, we use this to identify the target item before/after a drag/drop event.
<DraggabledraggableId={`comment-${_comment.id}`}index={index}key={_comment.id}>{(_provided, _snapshot) => (<Commentref={_provided.innerRef}
Props that should be passed to html element that will be used to drag this component.
dragHandleProps={_provided.dragHandleProps}
Additional props that need to be present inside the html element that needs to be draggable.
{..._provided.draggableProps}snapshot={_snapshot}{..._comment}/>)}</Draggable>);})}{provided.placeholder}</Comments>)}</Droppable></DragDropContext></div></div>);};export default App;
That’s all folks! Hope you learned something new!
Thank you so much for reading this!