DEV Community

Cover image for Drag and Drop Tables with React-Beautiful-DND (Part I)
milandhar
milandhar

Posted on • Updated on

Drag and Drop Tables with React-Beautiful-DND (Part I)

This week, I wanted to experiment learning a new React component and implementing it into my EffectiveDonate website. I began to think of what aspects of the site could use a cool new feature to improve its UX, and focused in on the Profile page. Previously, the Profile page allowed users to update their default themes (Health, Education, etc), and also to view the nonprofit projects that they had starred. The list of projects was organized in a Semantic UI Table, and enabled users to view key information about the projects, donate to the project, or delete the project from their stars. However, the table was sorted in chronological order, so that the user's most recent starred projects were all the way at the bottom of the table - not the best UX!

While I could have easily just sorted the table in reverse chronological order as a quick fix, I wanted to give the user some more control. So I started to brainstorm some solutions in React to make the table more dynamic. I found this List of Awesome React Components, and read through a list of several drag and drop components. Drag and drop would be a nice, clean way to let the user customize their starred projects! I eventually chose React Beautiful DnD - it had over 17k stars on GitHub, a nice instruction video and many examples.

Original Profile Page
The original profile page, with starred projects table in chronological order

What is React-Beautiful-DnD?

React-Beautiful-DnD is a React package with a goal of creating drag and drop functionality for lists that anyone can use, even people who can't see. The main design goal is physicality - they want users to feel like they are moving objects around by hand. It also has accessibility features, including drag and drop using just the keyboard.

It also plays nicely with tables, specifically the Semantic UI React Table component, which sealed the deal for me to use it.

Implementing React-Beautiful-DnD on my Website

In order to make my StarredProjectsList component DnD-able, I followed a video course on react-beautiful-dnd, and referenced this example of a Semantic UI table component. Also I made sure to install the package with: npm install react-beautiful-dnd --save.

While I recommend going through the two resources I listed above to thoroughly understand the process for implementing the component in your project, I'll give a few highlights of key components in the API here:

DragDropContext

This component is required to specify which part of your React tree you want to be able to use drag and drop. For me, I wrapped my entire Semantic UI Table component with <DragDropContext />. A required prop for this component is onDragEnd, a function that dictates how the list or table's state should change once the drag operation is complete. The opening tag for my DragDropContext is the following: <DragDropContext onDragEnd={this.onDragEnd}>.

The onDragEnd method finds the index of the starred project I dragged, and splices it into the array of my starredProjects state. Below is my implementation of the method:

  onDragEnd = result => {
    const { destination, source, reason } = result;

    // Not a thing to do...
    if (!destination || reason === 'CANCEL') {
      this.setState({
        draggingRowId: null,
      });
      return;
    }

    if (
      destination.droppableId === source.droppableId &&
      destination.index === source.index
    ) {
      return;
    }

    const starredProjects = Object.assign([], this.state.starredProjects);
    const project = this.state.starredProjects[source.index];
    starredProjects.splice(source.index, 1);
    starredProjects.splice(destination.index, 0, project);
    this.setState({
      starredProjects
    });
  }
Enter fullscreen mode Exit fullscreen mode

Droppable

A <Droppable/> is a container for </Draggable/> items. It can be dropped on by </Draggable />s.

The only required prop for <Droppable />s is a string, droppableId. I wrapped my <Table.Body/> in the <Droppable /> component, since that is the container of data on which I will be dragging rows.

Draggable

A <Draggable /> is the React component that will actually be dragged around onto <Droppable />s. It must always be contained by a <Droppable />, but it can also be moved onto other <Droppable />s.

The required props for <Draggable />s are: draggableId and index. Some important notes on these props:

1) the draggableId must be a string. I initially made mine an integer and was stumped when my table rows couldn't be dragged. But once I added the .toString() function to the prop, it was all good.
2) the index prop must be a consecutive integer [1,2,3,etc]. It also must be unique in each <Droppable />.

Below is a snippet of my code where I wrap each <Table.Row> in a <Droppable/> after maping each of the starred projects in state:

{this.state.starredProjects.map((project, idx) => {
 return (
     <Draggable
        draggableId={project.id.toString()}
        index={idx}
        key={project.id}
     >
       {(provided, snapshot) => (
       <Ref innerRef={provided.innerRef}>
         <Table.Row
     ...
Enter fullscreen mode Exit fullscreen mode

Children Function

Another quirk about the <Droppable /> and <Draggable /> components is that their React child must be a function that requires a ReactNode. If this child function is not created, the component will error out. The function contains two arguments: provided and snapshot. I recommend reading the documentation for both <Draggable /> and <Droppable /> to fully understand what these two arguments do and what props they take.

Also, the <Draggable /> and <Droppable /> components require an HTMLElement to be provided to them. This element can be created using the ref callback in React or the 'Ref' Semantic UI Component. This react-beautiful-dnd guide does a good job of explaining the purpose of the ref callback and how to avoid any errors.

For an example of how I used the provided and snapshot arguments of the child function, as well as the Ref Semantic UI Component in my table, here is a snippet of the <Droppable /> tag:

<Droppable droppableId="table">
    {(provided, snapshot) => (
       <Ref innerRef={provided.innerRef}>
          <Table.Body {...provided.droppableProps}>
          ...
Enter fullscreen mode Exit fullscreen mode

GIF of the working DnD Table
The working DnD table

Conclusion

Overall, it was a fun and informative process to implement my Semantic UI Table with react-beautiful-dnd. I enjoyed learning the component's API and it was interesting to work with concepts that were new to me, like the children functions and ref callbacks.

I definitely recommend viewing the video course on react-beautiful-dnd, and also checking out the example code online. You can also reference my table component file on GitHub to fully see how I implemented the DnD components.

While I am satisfied with the UX that is available on the table component now, the next step is to make it persist on the backend so that when the user refreshes the page, the table re-renders in the new order. This should require a bit of creative manipulation on the backend, which I am excited to tackle next week :)

Thank you for reading and let me know if you have any questions or comments!

Top comments (3)

Collapse
 
pnave95 profile image
Patrick Nave

Nice post! Do you have full code somewhere? I looked at your github link, but I don't see any styling for the table component.

Collapse
 
milandhar profile image
milandhar

Hi Patrick, thanks for reading. Here's the link to my full Github repo for the project: github.com/milandhar/mod5-project-...

The style sheet is in /src/App.css. However, for the table, I imported a Semantic UI React Table (react.semantic-ui.com/collections/...). In the 2nd line of the StarredProjectsList.js file, I have import { Button, Icon, Table, Flag, Ref } from 'semantic-ui-react'.

Let me know if that helps!

Collapse
 
haseebmohsin profile image
Haseeb Khan

Hi dear! please let me know if you have part 2 of this post anywhere else i really need to know how to implement the database part to make the changes permanent.