React Drag and Drop Sortable List

Arbaz Ajaz
4 min readOct 26, 2020

--

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.

  1. https://www.npmjs.com/package/react-beautiful-dnd
  2. https://www.npmjs.com/package/node-sass
  3. https://www.npmjs.com/package/react-icons
  4. 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

In case you’d like to replicate the structure, you can use this reference image.

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:

  1. comment: (the text content of the comment)
  2. date: (date in ISO format, at which this comment was posted)
  3. dragHandleProps: (any html elment that we need to use to drag this component around will have these props)
  4. 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)
  5. …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!

--

--

Arbaz Ajaz
Arbaz Ajaz

Written by Arbaz Ajaz

Arbaz is a MERN Stack developer, aiming to make learning programming accessible and straight forward

No responses yet