Creating an app using Drag and Drop with React without libraries π!
Franklin Martinez
Posted on July 20, 2022
Drag and drop applications are very common nowadays, they are great for the user experience inside an app. **And you would probably like to implement it in your next project.
This time, I'll show you how to make an application that has drag & drop functionality, but without using any external library, only with React JS.
π¨ Note: This post requires you to know the basics of React with TypeScript (basic hooks and custom hooks).
Any kind of feedback is welcome, thanks and I hope you enjoy the article.π€
βΆοΈ CSS vanilla (You can find the styles in the repository at the end of this post)
Β
π Creating the project.
We will name the project: dnd-app (optional, you can name it whatever you like).
npm init vite@latest
We create the project with Vite JS and select React with TypeScript.
Then we run the following command to navigate to the directory just created.
cd dnd-app
Then we install the dependencies.
npm install
Then we open the project in a code editor (in my case VS code).
code .
Then with this command we will raise the development server, and finally we go to a browser and access http://localhost:5173 (in vite version 2 the port was localhost:3000, but in the new version the port is localhost:5173)
npm run dev
Β
π First steps.
At once, we create the folder src/components and add the file Title.tsx and inside we add:
exportconstTitle=()=>{return (<divclassName="title flex"><h1>Creating basic Drag & Drop π </h1><span>( without external libraries )</span></div>)}
Now, inside the file src/App.tsx we delete all the content of the file and we place a functional component that shows the title that we have just created.
We will NOT use the Card component in a file yet, but if you want you can import it into the src/App.tsx file so you can style it and see it on screen.
Β
π Creating the containers for our cards.
Now let's create our container for the cards.
Inside the folder src/components we add the file ContainerCards.tsx and add the following:
Now, returned in src/components/DragAndDrop.tsx in component ContainerCards we pass a new prop called items to this prop we pass as value the data we have created in the folder src/assets.
2 - In the component src/components/ContainerCards.tsx we change the Props interface by adding the items property which is a list of Data and destructuring it in the component
This will give you a warning that the keys are repeated π₯.
This is because we are rendering 3 times the ContainerCards.
But wait the only property that will make the difference between these 3 components is the status.
So we will make the following condition:
If the status received by the ContainerCards component is equal to the status of the item (i.e. of the super hero) then render it, otherwise return false.
And so we avoid the conflict with the keys and the cards will be sorted as follows π....
Β
π Performing the Drag.
To perform the drag functionality, we will first define a state and a function in src/components/DragAndDrop.tsx.
The state will help us to know if it is doing drag, and thus to change the styles of.
And by default it will be false, since at the beginning of the application it will not be doing drag.
It will only be true when dragging a card.
The function, which receives a boolean value, will help us to change the value to the state, this is done to avoid passing the setter setIsDragging as prop.
We pass as prop to the ContainerCards component:
isDragging, it will have the value of the state.
handleDragging, will be the function that we create to update the state.
Note that in the className of the div we place a condition, where if isDragging is true then we add the class layout-dragging. This class will only change the background color and the border of the container, when a card is dragged.
Note, that we also pass a new prop to the CardItem which is handleDragging, this is because the card is the component that will update the state that we created previously.
And now yes, we begin to add the drag functionality in this component.
First to the div which is the whole card, we add the attribute draggable to indicate that this component can be dragged.
Then we add the attribute onDragEnd that will execute the function handleDragEnd.
This function will only set the value of the isDragging status to false, because when onDragEnd is executed, the card will no longer be dragged, so we have to remove the styles when dragging, that is, return all the styles as at the beginning.
Then we add the onDragStart attribute (it is executed when the component starts dragging, if we did not add the draggable attribute, then onDragStart would not be executed).
onDragStart will execute the handleDragStart function.
This function receives the event and inside the event there is a property that interests us which is dataTransfer.
The dataTransfer property allows us to contain or obtain data when an element is being dragged.
The setData property within dataTransfer, establishes the data that we want to contain when dragging an element, and receives two parameters:
format: format of the data to be maintained, which is "text".
data: is the information that we want to contain while dragging the element. It only accepts a string. In this case, we will store the id of the card.
NOTE: there is also a property inside dataTransfer called clearData that clears the cache of the data we store. In this case it is not necessary to execute it, since we will be overwriting the same identifier 'text'.
After containing the data, we execute handleDragging sending the value of true to indicate to the user that we are dragging an element.
And so we would have the part of dragging an element, we would already have the contained information ready to get it when we drop it in another container.
This is how it would look when we drag a card, it changes the design of the containers indicating that they are the places where you can drop the card.
Β
π Performing the Drop.
Before we do the part of releasing the element, we must do other things first.
π Creating the state to hold the cards.
First to establish the list of heroes in a state and to be able to update it when the card is dropped in another container, at that moment we would update the status property of the hero, which will cause that the list is rendered again organizing the cards that changed.
For that we go to src/components/DragAndDrop.tsx and create a new status.
Its initial value will be the data that we have previously defined in src/assets.
And now, when rendering the ContainerCards component, instead of passing the value of data to the items prop, we will send it the value of the listItems state.
Then we will create a function to update the state of the listItems.
We will call it handleUpdateList, and it will receive two parameters:
id: the identifier of the card, it will be of type number.
status: the new status of the card, it will be of type Status.
Inside the function ...
1 - First we will look for the element in the listItems status value, by means of the ID.
2 - We will evaluate if the data exists and if the status that is passed to us is different from the status that it already has, then we will make the changes in the status.
3 - Within the condition, we access to the found card and we will update its status property assigning it the new status that comes to us by parameter in the function.
4 - We call the setListItems to update the status, placing:
The card with its updated status property.
A new array, filtering the items to remove the card we are updating and avoid duplicating the information.
Now, to the ContainerCards component we add a new property called handleUpdateList and send it the function we just created handleUpdateList.
This will give us an error, because the ContainerCards component does not expect the handleUpdateList property, so we must update the ContainerCards interface.
π Performing the functions to make the drop in the containers.
We are in src/components/ContainerCards.tsx.
Inside the component we are going to set two new properties to the div element.
onDragOver*: occurs when a draggable element is dragged over a valid drop target. We pass it the **handleDragOver* function, which we will create in a moment.
onDrop*: occurs when the dragged element is dropped. We pass it the **handleDrop* function, which we will create in a moment.
First, you will receive the event that emits onDrop.
Inside the function, we avoid the default behavior, which is more noticeable with images (when we drop an image in a place of our app, it opens the image, taking us out of the app).
Then, from the event, we obtain the property dataTransfer and through the getData property of dataTransfer, we execute it sending the identifier from which we will obtain the ID of the card.
The + sign at the beginning of e.dataTransfer.getData('text') is to convert the value to a number.
Then we will call the handleUpdateList function that the component passes us by props, (we have to unstructure it from the component).
First we pass it the id that we obtained from the getData property of dataTransfer already converted to a number.
Then we pass it the status that we received by props in the component.
Finally we call handleDragging sending the value of false to indicate to the user that we are no longer dragging anything.
This process is one of the ways to build an application with Drag & Drop functionality without using external libraries.
One way to improve this application would be to use a state manager to avoid passing too many props to the components.
If you want something more elaborate and expand the functionality, you can opt for a third party package that I highly recommend, and that is react-beautiful-dnd, a very good and popular library.
I hope I helped you understand how to perform this exercise,thank you very much for making it this far! π€β€οΈ
I invite you to comment if you find this article useful or interesting, or if you know any other different or better way of how to do a drag & drop. π