Create draggable components with React-Draggable

5 min read 1511

Draggable Components React Draggable

Simple and straightforward to use, the React-Draggable library applies CSS transformations to React components, allowing you to drag components across a UI. With different props that let you change components’ behavior, React-Draggable is a great choice for creating intuitive, user-friendly interfaces.

In this tutorial, we’ll explore React-Draggable in depth by creating a task list with draggable components. Let’s get started!

Installing React-Draggable

Before installing React-Draggable, make sure you have a React project set up on your local machine. Navigate to the project and run the following command in the terminal:

npm install react-draggable

Now, we’ll import the <Draggable/> component from the library. To add movement to an element or a component, we can simply wrap the <Draggable/> component around it.

Add the following code to App.js:

import Draggable from "react-draggable";

function App() {
  return (
 <Draggable>
      <div>I can now be moved around!</div>
    </Draggable>
  );
}

export default App;

The code above will render the following output in the browser:

Draggable Components React Draggable

Now that you know how the React-Draggable library works, let’s get started on our task list.

Setting up a React app

With the React-Draggable library installed, we can set up the required file structure for our project and review the components that we’ll need. To assign unique keys to key components, we’ll need to install a UUID() as follows:

npm i uuid

Our folder structure will look like the image below:

We made a custom demo for .
No really. Click here to check it out.

React Draggable Project Folder Structure

Creating the task list

We’ve broken down the logic for our project into three different components. Our task list will use three columns to render their respective tasks.

We’ll add a form element to take input from the user and add the task in the respective component. Our App.js file handles the main task list in its state:

import "./App.css";
import { useState } from "react";

function App() {
const [tasks, setTasks] = useState([]);
  const [addItem, setAddItem] = useState(false);
  const handleSubmit = () => {
    setAddItem(!addItem);
  };
  const addTask = (task) => {
    setTasks(task);
  };
  return (
  <div>
 </div>

  );
}


export default App;

In the code above, we’ve added the following functions:

  • tasks: holds the array of tasks that will be rendered on the screen
  • addItem: the state for showing and hiding the AddForm element, which adds a task

Now that we have the states ready, let’s create the other components.

Header component

Add the following code to Header.js:

import React from "react";

export default function Header({ handleSubmit }) {
  return (
    <div className='header'>
      <button onClick={handleSubmit} className='addButton'>
        Add Task
      </button>
      <p>Planning</p>
      <p>In Progress</p>
      <p>Done</p>
    </div>
  );
}

The </button> gets the handleSubmit function as a prop, which will show the AddForm element. With our header set up, let’s create the AddForm element:

AddForm element

Add the following code to AddTaskForm.js:

import React, { useState } from "react";
import { v4 as uuidv4 } from "uuid";

export default function AddTaskForm({ setAddItem, addItem, tasks, setTasks }) {
  const [name, setName] = useState("");
  const [description, setDescription] = useState("");
  const [option, setOption] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    let newTask = {
      id: uuidv4(),
      name: name,
      description: description,
      timeline: option,
    };
    setTasks([...tasks, newTask]);
    setAddItem(!addItem);
  };
  return (
    <div className='addForm'>
      <form onSubmit={(e) => handleSubmit(e)}>
        <input
          type='text'
          placeholder='Name'
          onChange={(e) => setName(e.target.value)}
        />
        <input
          type='text'
          placeholder='Description'
          onChange={(e) => setDescription(e.target.value)}
        />
        <select
          name='timeline'
          id='timeline'
          onChange={(e) => {
            setOption(e.target.value);
          }}
        >
          <option value=''></option>
          <option value='planning'>Planning</option>
          <option value='inprogress'>In-Progress</option>
          <option value='done'>Done</option>
        </select>
        <button type='submit'>Add</button>
      </form>
    </div>
  );
}

In the code block above, we have input fields that will take relevant data provided by users. Each task has an id, name, date, and timeline , which are taken by the select element.

When the form is submitted, each task will be added to the task list that we created in App.js. When the form is submitted successfully, it will be hidden.

Creating the columns

In our task list, we’ll have three columns titled Planning, In Progress, and Done. Let’s create these components and add their respective code:

Planning.js

import React from "react";
import Card from "./Card";

export default function Planning({ tasks, addTask }) {
  return (
    <>
      {tasks
        .filter((item) => item.timeline === "planning")
        .map((e) => (
          <Card currentTask={e} tasks={tasks} addTask={addTask} />
        ))}
    </>
  );
}

InProgress.js

import React from "react";
import Card from "./Card";

export default function InProgress({ tasks, addTask }) {
  return (
    <>
      {tasks
        .filter((item) => item.timeline === "inprogress")
        .map((e) => (
          <Card currentTask={e} tasks={tasks} addTask={addTask} />
        ))}
    </>
  );
}

Done.js

import React from "react";
import Card from "./Card";

export default function Done({ tasks, addTask }) {
  return (
    <>
      {tasks
        .filter((item) => item.timeline === "done")
        .map((e) => (
          <Card currentTask={e} tasks={tasks} addTask={addTask} />
        ))}
    </>
  );
}

You may have noticed that all of these components hold a similar logic. We are getting the tasks and filtering them based on their timeline using tasks.filter(), which removes the task objects that do not belong in the column.

For example, in the planning component, filter() will remove every object that does not have the planning string in its timeline attribute.

The array returns a result and renders a <Card/> component. Let’s create Card.js in the same directory and add the code snippet below:

import React from "react";
import Draggable from "react-draggable";

export default function Card({ currentTask, tasks, addTask }) {
  return (
    <Draggable grid={[10, 10]} axis='y' bounds='parent'>
      <div className='card' key={currentTask.id}>
        <div className='heading'>
          <h3>{currentTask.name && currentTask.name}</h3>
          <img
            onClick={() => {
              const newTaskList = tasks.filter((item) => {
                if (item.id != currentTask.id) {
                  return item;
                }
              });
              addTask(newTaskList);
            }}
            src='https://toppng.com/uploads/preview/recycling-bin-vector-delete-icon-png-black-11563002079w1isxqyyiv.png'
            style={{ height: "20px", width: "20px" }}
          />
        </div>
        <p>{currentTask.description}</p>
      </div>
    </Draggable>
  );
}

The <Card/> component gets three props, which are required for rendering and deleting logic.

The parent div <Draggable> is imported from our library, making the whole card moveable. By default, you can move the card anywhere on the screen. However, to prevent it from going outside of its parent element, we’ll provide a value to a bounds prop.

Deleting the task

For removing a task, add the code below:

  const newTaskList = tasks.filter((item) => {
    if (item.id != currentTask.id) {
      return item;
    }
  });

  addTask(newTaskList);

The code block above gets the entire list of tasks from App.js, then creates a new list that excludes the current task. Next, let’s import each component in App.js:

import "./App.css";
import { useState } from "react";
import Header from "./Header";
import Planning from "./Planning";
import InProgress from "./InProgress";
import Done from "./Done";
import AddTaskForm from "./AddTaskForm";
function App() {
  const [tasks, setTasks] = useState([]);
  const [addItem, setAddItem] = useState(false);
  const handleSubmit = () => {
    setAddItem(!addItem);
  };
  const addTask = (task) => {
    setTasks(task);
  };
  return (
    <div>
      <Header handleSubmit={handleSubmit} />
      <div className='mainGrid'>
        <div className='column'>
          <Planning tasks={tasks} addTask={addTask} />
        </div>
        <div className='column'>
          <InProgress tasks={tasks} addTask={addTask} />
        </div>
        <div className='column'>
          <Done tasks={tasks} addTask={addTask} />
        </div>
        {addItem && (
          <AddTaskForm
            addItem={addItem}
            setAddItem={setAddItem}
            tasks={tasks}
            setTasks={addTask}
          />
        )}
      </div>
    </div>
  );
}

export default App;

Finally, we’ll add styles to our task list. Add the following code in App.css:

.mainGrid{
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  flex-wrap: wrap;
  height: 100vh;
}
.header{
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  flex-wrap: wrap;
  background-color: rgb(205, 240, 240)
}
.column{
  width: 32vw;
  display: flex;
  flex-direction: column;
  border: 5px solid grey;
  background-color: azure;
  overflow: hidden;
}
.card{
  border: 1px solid black;
  background-color: white;
}
.card p{
  font-size: 10px;
  width: 30vw;
}
.heading{
  display: flex;
  flex-direction:row;
  align-items: center;
  justify-content: space-between;

}
.addButton{
  position:absolute;
  top: 2.5%;
  left: 0%;
}
.addForm{
  height: 50vh;
  width: 50vw;
  background-color: lightblue;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  position: absolute;
  top: 25%;
  left: 25%;
}
.addForm form{
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column; 
}

When we run the project and add an item to our task list, the output will look like the image below:

Final Task List Three Columns-react-draggable

These tasks are moveable within their respective columns:

React Draggable Components Movement

To delete a task, simply click on the delete icon.

Conclusion

Adding movement to components in your application has benefits for your UI like improved visual interest and better organization. The React-Draggable library is a great choice for its simplicity and functionality.

In this tutorial, we used React-Draggable to create a task list. To learn more about React-Draggable, be sure to visit the GitHub page.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Testing accessibility with Storybook

One big challenge when building a component library is prioritizing accessibility. Accessibility is usually seen as one of those “nice-to-have” features, and unfortunately, we’re...
Laura Carballo
4 min read

Leave a Reply