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.
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:
javascript
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
});
}
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 map
ing each of the starred projects in state:
javascript
{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
...
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:
javascript
<Droppable droppableId="table">
{(provided, snapshot) => (
<Ref innerRef={provided.innerRef}>
<Table.Body {...provided.droppableProps}>
...
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)
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.
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!
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.