35

Implement Better Drag and Drop in your React App

 5 years ago
source link: https://www.tuicool.com/articles/hit/7VBZ7ju
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

How To Implement Better Drag and Drop in your React App

With the react-beautiful-dnd package. Learn more.

FZjqmuA.png!web

The phrase drag-and-drop is used to describe the action of selecting an object, dragging it to wherever you desire, and then letting go or dropping it at that place on your screen.

We use drag-and-drop everyday on our device. We use it to select text in our documents. We use it to move our icons all over the screen.

The best and well known use-case of the drag-and-drop function is something called Trello .

3aiEz26.gif

Though drag and drop looks simple to the eye, a developer needs to take care of a lot of things in order to implement drag-and-drop in their apps. Some of these things are:

  • Deciding which object should be draggable and which object should not be draggable.
  • The areas in our app where the draggable objects can be dropped.
  • Making sure that the drag-and-drop function does not break other functions of our apps.
  • Doing all this while keeping user experience in mind.

Implementing Drag-and-Drop in our app from scratch can take a while. In this post, I will show you how to use the react-beautiful-dnd package to easily implement a good drag-and-drop in our React app.

Shared with ❤ in Bit’s blog

Bit helps your team build faster by sharing components between apps. Collaborate, suggest updates, sync changes and build amazing apps. It’s free.

Getting Started

Let’s start by cloning this GitHub Repository that I have created.

$ git clone https://github.com/rajatk16/infinity-war.git
$ cd infinity-war
$ npm install

This will create a new React App named infinity war . Run the start script using NPM/Yarn to run the development server.

$ cd <project-name>
$ yarn start

By now, you must have guessed that this example app here has something to do with the movie Avengers: Infinity War.

In this app, I am first going to print a list of characters from this movie, and then later on, we will see how to drag these into two lists: those who survived, and those who didn’t.

We can now see that React is listing out the name of the Heroes like this:

ZzMBBfz.gif

Let’s get started with adding some Drag-and-Drop functionality to our app in the next section.

Re-ordering the List

Before we get started, lets install react-beautiful-dnd package as a dependency.

$ yarn add react-beautiful-dnd

The react-beautiful-dnd package consists three components.

DragDropContext
Droppable
Draggable

Start by importing the DragDropContext into the App/index.js file.

import {DragDropContext} from 'react-beautiful-dnd';

We will then use this component to wrap the code that renders the columns in our app.

<DragDropContext>
  {this.state.columnsort.map(columnId => {
    const column = this.state.columns[columnId];
    const heroes = column.heroId.map(heroId => this.state.heroes[heroId]);
    return <Column key={Column.id} column={column} heroes={heroes} />;
  })}
</DragDropContext>

Let’s leave this as it is for now and go update the Column component. Start by importing the Droppable component from react-beautiful-dnd package and use to wrap the div with classname of Hero-List . The Droppable component also has one prop named droppableId . The Droppable component also expects its child to be a function that returns a React component. A great advantage here is that React does not need to create any new DOM nodes.

The function here will take an argument called provided . This argument is an object that serves a number of purposes:

  • droppableProps are props that need to be applied to any component that you want to deem as draggable .
  • innerRef is a function that is used to supply the DOM node of the component to the react-beautiful-dnd package.

So update the Droppable component with the following code:

<Droppable droppableId={this.props.column.id}>
  {(provided) => (
    <div 
      className="Hero-List" 
      innerRef={provided.innerRef} 
      {...provided.droppableProps}
    >
      {this.props.heroes.map((hero, index) => (
        <Hero key={hero.id} hero={hero} index={index} />
      ))}
      {provided.placeholder}
    </div>
  )}
</Droppable>

The placeholder is React element that is used to increase the available space in a droppable component while a drag is going on.

Next, we will go to the Hero component and make it draggable. Start by importing the Draggable component into the file.

import {Draggable} from 'react-beautiful-dnd';

Wrap the div with className of Container with this Draggable component. The Draggable component also has two props : The draggableId and index .

<Draggable 
draggableId={this.props.hero.id}
index={this.props.index}
>

<div className="Container">
{this.props.hero.name}
</div>
</Draggable>

Similar to the Droppable component, the Draggable also expects its child to be a component. So let’s take care of it by rewriting the Draggable component as follows:

<Draggable 
  draggableId={this.props.hero.id} 
  index={this.props.index}
>
  {provided => (
    <div className="Container"
      {...provided.draggableProps}
      {...provided.dragHandleProps}
      innerRef={provided.innerRef}
    >
      {this.props.hero.name}
    </div>
  )}
</Draggable>

You will now see that we can drag things around in our column like this:

AFrUjyM.gif

But Ooops! We now see that the list doesn’t set to the new order of objects and reverts back to how it originally was.

The DragDropContext has three callbacks:

onDragStart
onDragUpdate
onDragEnd

I only need the onDragEnd callback. Let’s add it to the DragDropContext component inside the src/App :

<DragDropContext onDragEnd={this.onDragEnd}>

Then create the onDragEnd helper function inside the same class as shown below:

onDragEnd = result => {
  const {destination, source, draggableId} = result;
  if (!destination) {
    return;
  }
  if (
    destination.droppableId === source.droppableId &&
    destination.index === source.index
  ) {
    return;
  }
  const column = this.state.columns[source.droppableId];
  const newHeroIds = Array.from(column.heroIds);
  newHeroIds.splice(source.index, 1);
  newHeroIds.splice(destination.index, 0, draggableId);
  const newColumn = {
    ...column,
    heroIds: newHeroIds,
  };
  const newState = {
    ...this.state,
    columns: {
      ...this.state.columns,
      [newColumn.id]: newColumn,
    },
  };
  this.setState(newState);
};

Here, the source and destination objects contain the info about where our Draggable started and finished.

The first conditional statement is for the case where there is no destination . In such cases, we don’t have to do anything, so we simply return nothing.

Next, we need to check it the location of the draggable has changed. To do that we check whether the droppableId and index of the destination and source are the same. If they are, then that mean the location has not changed, and we don’t need to do anything again.

Then we are re-ordering the heroIds array for that column . First we extract the column from the state, then I will create a new array and store it with the same data that we just extracted from the state.

After that, I am moving the heroId from its old index to the new index using splice method. I will then use splice to insert the draggableId , which is actually the heroId .

Finally, we will create a whole new column that has the same properties as the old one, but with the newHeroIds array. We will then insert this into our newState and set that as our App’s new state.

YfeiUfE.gif

Move Items between Columns

We can now move objects vertically within the same column. But what about moving things to other columns? We haven’t implemented that functionality yet.

Let’s start by creating a new Container component in the src/App file.

const Container = styled.div`
  display: flex;
`;

Use this component to wrap the column-related code inside the class.

<Container>
  {this.state.columnsort.map(columnId => {
    const column = this.state.columns[columnId];
    const heroes = column.heroIds.map(heroId => this.state.heroes[heroId]);
    return <Column key={Column.id} column={column} heroes={heroes} />;
  })}
</Container>

This will render the columns horizontally instead of vertically.

7VreUzF.png!web

But as you can see, the second and third columns are way too small. To fix this tiny issue, go to the Column/index.js file and add a width property to the Container component. We will also add some flexbox properties

const Container = styled.div`
margin: 10px;
border: 1px solid lightgrey;
border-radius: 5px;
width: 33%;
display: flex
flex-direction: column
`;

This has equalized width of all three columns and created a flexbox whose children are vertically aligned. Similarly, I want to make the HeroList component to “grow”.

const HeroList = styled.div`
  padding: 10px;
  flex-grow: 1;
  min-height: 100px;
`;

We now face the same issue that we before. When we drag and drop an object into another column, it reverts back to the same column. Let’s fix this by making some changes to the onDragEnd helper function in the App/index.js file.

Here we had created a const named column . Rename this to something like begin and create another const named end .

const begin = this.state.columns[source.droppableId];
const end = this.state.columns[destination.droppableId];

Now if both the begin and end are the same, that means we are doing a drag and drop in the same column. So we can use the same code that we were using before, just wrapped inside a if statement.

if (begin === end) {
const newHeroIds = Array.from(begin.heroIds);
newHeroIds.splice(source.index, 1);
newHeroIds.splice(destination.index, 0, draggableId);
const newColumn = {
...begin,
heroIds: newHeroIds,
};
const newState = {
...this.state,
columns: {
...this.state.columns,
[newColumn.id]: newColumn,
},
};
this.setState(newState);
return;
}

Now let’s take care of the scenario where begin and end are not the same, meaning, the user is trying to move an object from one column to another.

Create a new array named beginHeroIds that contains the same ids as the old array. We will then remove the dragged heroId from this array, create a newBegin column, put the same properties in it as the old column along with the newBeginIds array.

const beginHeroIds = Array.from(begin.heroIds);
beginHeroIds.splice(source.index, 1);
const newBegin = {
  ...begin,
  heroIds: beginHeroIds
};

Similarly, we will create another array named endHeroIds that contains the same heroIds as the last end column. We will use the splice operator to insert the draggableId at the destination.index . We will then create another new column named newEnd .

We will then create a newState object which will have the same properties as the old state, but with updated columns.

const newState = {
  ...this.state,
  columns: {
    ...this.state.columns,
    [newBegin.id]: newBegin,
    [newEnd.id]: newEnd,
  },
};
this.setState(newState);

With this, we can now drag and drop our object in different columns.

aMveYnn.gif

Conclusion

We can do a lot more with this awesome package. For instance, we can drag and drop entire columns, or disable the drag and drop for certain objects in a column.

Drap and Drops are great. They help improve the user experience significantly. I hope this post helped you understand how to implement Drag and Drop in your React App.

You can check out the final version of this app here:

I am Rajat S . An aspiring coder who has a long way to go. A Die-Hard DC Comics Fan who loves Marvel Movies. :stuck_out_tongue: Thank you for reading! Please do :clap: if you liked it and feel free to comment and ask anything!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK