Sortable Targets with React DnD

If you ever had to add some drag and drop functionality to your app, chances are you suffered a bit. For those building their apps with React, however, React DnD does not fall in this category. The API is clean, the docs are comprehensive and there are many examples.

This library provides a thin layer to cover your components. First add a drag and drop context, then start connecting your components with the functionality you want. If you haven’t had a chance to test it yet, go there and fiddle around.  Read the docs to understand the mindset behind.

The aim of this post is not to explain or advocate for React DnD (I’m positive his author, Dan Abramov, would do a better job). I will also steer clear from Babel and Webpack configuration (let’s reduce the tooling overhead). Instead, I would like to focus on the code and add a new use case.

But if you do want to know the insides, I got you covered: there’s a reproducible repository set on GitHub.

The Problem

Imagine you have many elements and you want to drag and drop them into different containers. You are also asked to sort them. How one would solve this?

The docs provided almost what we want. There is a sortable example and a single target example. Our goal is to combine them and build a sortable multi targets example.

If you still are having problems picturing this – don’t worry! Check the example right below.

Note that every container is a target. When dropping elements into other containers, they are pushed to the end. When inside, they are sorted. Pretty simple.

The Rationale

I’ll not extend myself too much because there isn’t anything really new here. If you have understood the examples I cited before, you will have no trouble in following this. Saying that, my focus will be in how to go from the examples to the app I just presented.

There are 3 components in our app:

  1. App, a container component that is connected to the application and plugs the DragDropContext;
  2. Container, a component that will hold every card and act like a target for other cards;
  3. Card, a component that will be dragged around (either to other Containers or inside the one it is in).

Now should be simple to identify that we have one drag source (the Card) and two drop targets (the Container and also the Card). When dragging Cards outside its Container, we need to remove it to its original Container and push to the new. When dragging inside, sort them.

Let’s start from the beginning.

The App Component

This component is straightforward. Just render the Containers on the screen and give them some props. What props?

Well, definitely the Container should know which cards it is holding. This should be one.

Also, we need a way to distinguish Containers. Remember that we sort Cards being dragged inside its parent Container and push when it’s a new one. So we might pass an id as well.

Our App component should be something like this:

We are defining some style using Flexbox, hardcoding our lists and creating our Containers. Note the last line, when we tell React that App is a DragDropContext with HTML5 Backend. Check the docs if you want a more in-depth explanation.

The Container Component

This is the main component, responsible for managing the state (cards currently inside him) and being a drop target.

Note that we change the Container state in 3 different situations:

  1. A Card is pushed, meaning we need to add it to the state;
  2. A Card is pushed into another container, meaning we need to remove it from the state.
  3. A Card is moved inside the Container, meaning we need to sort it.

This is how I would implement it:

Each method should be straightforward. We are using React’s update helper, a handy Immutability Helper that I strongly recommend you to learn. Some methods might sound cumbersome, but once you get the grasp you follow through pretty easily.

Also note that the moveCard method is identical to the one provided in the sortable example. Talk about reusability!

Now let’s write the render method for our class:

There is nothing really new here. Some background styling and an iteration in order to render the Card component and connectDropTarget to tell we might expect some dropping to happen here.

Check that we are passing listId, removeCard and moveCard as props. This is necessary because we have to know in which Container we are, as well wich actions we need to perform. Note that pushCard is an event related to the Component, so we don’t need Card to handle it.

Finally, let’s wrap this class up:

There are some important things happening here:

  1. We are importing DropTarget;
  2. Our cardTarget object has a drop callback. This function analyses if the Container’s id is different from the Container’s id of the object being dropped. If positive, then we push the element. We don’t need to push elements when the Containers are the same – in this case we are just moving Cards around. The return is an object with the Container’s id. This is necessary because we need a way to know if a Card from its original Container should be removed. More on that later.
  3. Finally we just connect things together and export the class. Nothing new if you are familiar with the Single Target example.

Now the last piece of the puzzle!

The Card Component

Let’s start with the easy part: rendering and styling.

You should be comfortable with this code. We are styling the Card and defining some opacity effect.

Now remember that Card has two behaviors: it is a source and a target, because it is able to be dragged but also to be reordered. Let’s see the source callbacks:

  1. beginDrag returns an object with useful properties when dragging event is over.
  2. endDrag handles what to do when we finished dragging. Any resemblance with the drop callback we just wrote? We are doing the same check, but now in the Card’s realm. If we successfully dropped the Card in a target (a Container) and the id’s are different (meaning we moved a Card from one Container to another), then call the removeCard method from the Container. Both ends are tied together!

The missing piece here is the callback when the Card is the target, i.e., the sort event. Luckily only a couple of changes to the original from the Sortable example need to be made.

The difference is now we are grabbing the sourceListId, the id from the element being dragged around, and comparing with the id from the current component. If they are the same, this is a “Cards being hovered over Cards inside the same Container” situation – sort them. If they are different, don’t dispatch any hover event.

Finally connect the callbacks (note I’m avoiding using decorators. Instead, lodash flow function allows me to connect more than one function to the component.

And we are done! Note we can spawn as many Containers as we like and this will still work.

Final Considerations

Don’t forget to check out the repo set for this example. Have a good look at the source code and play around if you want.

From here, a possible improvement is to sort Cards in other Containers while they are being dragged. This is not trivial once we need to remove it from the original Container but push it back if the drop place is not a valid target. Wanna have a shot? 🙂

Don’t hesitate in leaving a message if you have any question, observation or correction to do. Feel free to contact me as via Twitter as well. Special thanks for Dan Abramov and his awesome docs and library – the React community appreciates your hard work.

3 Comments on “Sortable Targets with React DnD

  1. Join to the last comment from Don!
    I’ve implemented my version of the same already, but your decision works faster and has got the better way for extensions! Thank you!

  2. Excellent work. I wish there was something like this in the official react-dnd examples.

Leave a Reply

Your email address will not be published. Required fields are marked *