Drag and Drop with the GRANDstack

Adding Neo4j GraphDB manipulation via React Beautiful dnd

--

Graph of a table with columns and tasks

This tutorial assumes you have basic familiarity with React, Apollo, and Neo4j

While planning my most recent side project, I decided to play with a feature that I’ve always wanted to mess with on the front end, drag and drop functionality. It didn’t take long to find out that there are a number of highly regarded drag and drop libraries for React but, after reading docs and reviews I decided that React-beautiful-dnd was going to fit my use case. In addition, it came boxed up with a very nice free tutorial course which you can find here. None of the code pertaining to the drag and drop functionality is mine, I adapted it from the tutorial, my only contribution being that I created it with hooks vs. class components. You’ll need to complete the React-beautiful-dnd tutorial before getting the next step.

Let's get started!

After you’ve completed the drag and drop tutorial from Egghead, to start here all you need to do is pick up the starter GRANDstack project, clone it and get it spun up in your preferred IDE. After you’ve got the project up and running we’ll need to add these types to your schema.graphql file:

When our data is added our graph will look something like this.

Initial Graph

Let's go ahead and add data to our graph, open the Neo4j desktop, copy and paste this Cypher code:

This will create the graph structure we’re after. Next, run these two Cypher commands:

and

This sets up the initial ids and ensures that our columns start out correctly. With that done we’ll be able to get started.

Here’s a link to GitHub repository for the completed project. You’ll be picking up at the point where you’ve got multiple columns and are able to swap the order of tasks and also swap them between columns. Up until this point, there’s been no back end for the project so any changes that you’ve made will be undone when you refresh the browser or navigate away. Additionally, we’re getting our application state from an object that’s been created vs. calling API and that’s what we’ll add and fix next.

Our current column set up

If you haven’t cloned the repo and have instead been following along with the Egghead.io tutorial adding Apollo to our project is going to be easy. Simply install it with yarn or npm whichever your preferred method for me, it’s yarn:

In previous versions of Apollo you’d need to install quite a few other packages but in V3 they all come bundled together. After we’ve installed Apollo we need to create a new client in the root of our application:

And that’s all we need to get up and running with Apollo Client, make sure you’ve changed the appropriate environment variables or pointed the client at the correct locally running GraphQL API. With that done we’re able to go ahead and start querying our Neo4j instance and making the application update and maintain our data in real-time. In our App.js file, we’re going to add a GraphQL query and some mutations that will allow us to capture our application’s state. First, we’ll need to import our needed tools from @apollo/client:

Then we can create our query, for brevity I’m including this in the App.js file but as the size of your application grows you might consider breaking queries and mutations out into their own files. First, we’ll want to get our table or page and it’s associated columns and tasks from our Neo4j instance.
In this case, I’m calling the table by name:

This query allows us to get the specific table we’re after. It pulls the columns out and tasks along with it. In order to use the query we need to add it to our component:

This allows us to directly query our Neo4j instance and get that data we need but first, we’ll need to make some changes to the application as a whole and manipulate the data returned to fit our current structure.

Data Object From Egghead tutorial
At the current state of the application, you should be using this initialData object to set your state. However, now that we’re going to be pulling data in via our API well need to change it from this:

to this:

This gives us the structure of the data we expect before the application is actually able to load it, keeping us from getting rendering and null errors. To ensure that we’re getting our data correctly from the API and not encountering async errors we’re going to add useEffect and make use of Apollo’s loading, and error states.

These actions take place before the component has rendered allowing data to be fetched and more importantly for our fetched data to be reshaped into the form our application is expecting. We do this in our setTable function, which is called in useEffect once it’s verified that we have data.

This step is important because our data returned from our GraphQL API is in the shape we requested in it from our GET_TABLE query, and needs to be reshaped in order to properly fit our application. As it is, this gives us a basic framework to start saving the state changes of our data in our database.

Saving Column Order
The first thing we’re going to add to the application is the ability for the application to save changes in the order of tasks on a particular column. To do this, we’ll add a mutation to update the state of the column, this Mutation is automatically created for us by the GRANDstack’s augmented schema functionality. In our application, we need to send the mutation with all of the info that the column has and in this case, we’re interested in returning the column ID.

We’ll then add the useMutation hook to our application:
const [colUpdate] = useMutation(COL_UPDATE)
I’ve omitted the optional error and data properties and I’ll be handling this in a very simple way in our onDragEnd function. Where there’s a column update we’ll add the update function, pardon the wall of text that follows:

You’ll see that after the new column state is updated, we do the same with our UpdateColumn Mutation changing the order of the taskIds array and preserving the order of the tasks. At this point, our application will be saving the order of the tasks no matter what column they’re moved to but it will also be duplicating tasks because we’re not removing them from their old columns. Also because this data is stored in a GraphDB we need to swap the relationships as well. Meaning that when the task moves from one column we have to sever the relationship with that column and create a new [:BELONGS_TO] relationship with the new column. We accomplish this with another set of auto-generated mutations:

These mutations allow us to remove the relationship between a task and a column and then also create a new relationship between the same task and a new column. We bring these useMutation hooks in as:
const [addTask] = useMutation(ADD_TASK);
const [removeTask] = useMutation(REMOVE_TASK);

and add them into our onDragEnd function along with our UpdateColumn mutation to capture all the changes occurring when we swap a task between columns.

The promise chaining is a little ugly but it works and now our tasks properly change relationships when moved. In our original graph we had:

Starting Graph

And now we’re able to see our changes if you move “Task 1” to “Test Column 2” you’ll get this result from your graph:

Dragging triggers mutations to sever and create new relationships
Column 2 now has it’s own task

And finally, move “Task 3” to “Test Column 3” and you’ll end up with:

All three columns now have tasks
All 3 Columns have Tasks!

And now we’ve got drag and drop functionality enabled in our GRANDstack application. You can see that it’s a little more complicated than it might be with a SQL database because you have to work about the relationships but luckily the auto-generated mutations and Apollo make it super easy to work with. So go forth and drag and drop all the things!

(9/15/2020): Update, I’ve recently add the code to add tasks to our columns. You can find it in the included GitHub Repo.

(10/26/2020): The app now was the ability to delete tasks and initial functionality to edit task content.

(5/16/2022): Git Repo for API https://github.com/MuddyBootsCode/neo4j-drag-drop-api

--

--