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!
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:
Now that you know how the React-Draggable library works, let’s get started on our task list.
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’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 screenaddItem
: the state for showing and hiding the AddForm
element, which adds a taskNow that we have the states ready, let’s create the other components.
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
elementAdd 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.
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.
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:
These tasks are moveable within their respective columns:
To delete a task, simply click on the delete icon.
The react-draggable API supports a list of callback methods to track the movement of the draggable element:
onDrag()
— This method listens to the drag
event and gets triggered when drag
is in processonStart()
— This method listens to the drag
event and gets triggered when dragging startsonStop()
— This method listens to the drag
event and gets triggered when dragging stopsonMouseDown()
— This method gets triggered when the mouse is pressed on a draggable elementonMouseUp()
— This method gets triggered when the mouse is released on a draggable elementonTouchStart()
— This method gets triggered in the touch environment before drag startonTouchEnd()
— This method gets triggered in the touch environment before drag stopsimport Draggable, {DraggableCore} from 'react-draggable'; import React from 'react-dom' function MyDraggableComponent() { const eventHandler = (e, data) => { console.log('Event Type', e.type); console.log({e, data}); } return ( <Draggable defaultPosition={{x: 0, y: 0}} onMouseDown={eventHandler} onStart={eventHandler} onDrag={eventHandler} onStop={eventHandler}> <div style={{border: "2px solid red", padding: "1rem", width: "30%"}}> <div className="handle">Drag from here</div> <div>This readme is really dragging on...</div> </div> </Draggable> ) } export default MyDraggableComponent;
The handler
function receives the event
and data
objects as parameters.
Additionally, the data
object contains information about the current position of the draggable component.
axis
The axis
prop determines on which axis the draggable can move. It receives the following string values:
both
allows movement horizontally and vertically. This is the default value for the axisx
limits movement to the horizontal axisy
limits movement to the vertical axisnone
stops all movement<Draggable axis="x"> <div style={{border: "2px solid red", padding: "1rem", width: "30%"}}> <div className="handle">Drag from here</div> <div>This readme is really dragging on...</div> </div> </Draggable>
handle
The handle
prop defines a selector that will be used as the drag handle.
Without the handle
prop, the draggable component will be used as the drag handle:
<Draggable handle=".handle"> <div style={{border: "2px solid red", padding: "1rem", width: "30%"}}> <div style={{backgroundColor: "green", width: "30%"}} className="handle"> Drag from here </div> <div>This readme is really dragging on...</div> </div> </Draggable>
With the above snippet, we can move the draggable component by clicking on the green background color element.
defaultPosition
The defaultPosition
prop specifies the x
and y
coordinate that the dragged item should start:
<Draggable defaultPosition={{x: 0, y: 0}}> <div style={{border: "2px solid red", padding: "1rem", width: "30%"}}> <div className="handle">Drag from here</div> <div>This readme is really dragging on...</div> </div> </Draggable>
disabled
The disabled
prop receives a Boolean value. If true
, it will not call any drag handlers, making the draggable component operate like a completely static component.
bounds
The bounds
prop specifies the movement boundaries. It receives the following string values:
parent
— The parent
value restricts movement within the node’s offsetParent
(the nearest node with position relative or absolute)left, top, right, and bottom
properties, which indicate how far in each direction the draggable can be moved<Draggable bounds={{left: 0, top: 0, right: 100, bottom: 200}}> <div style={{border: "2px solid red", padding: "1rem", width: "30%"}}> <div className="handle">Drag from here</div> <div>This readme is really dragging on...</div> </div> </Draggable>
With the above snippet, the movement of our draggable component will be restricted to the specified values.
For more information on draggable props, check the documentation.
findDOMNode
deprecation errorIf the draggable component is running in React Strict mode, the warning ReactDOM.findDOMNode()
is deprecated will be logged on the browser’s console.
The fix to avoid this warning is to pass a nodeRef
as in this example:
function MyDraggableComponent() { const nodeRef = React.useRef(null); return ( <Draggable nodeRef={nodeRef}> <div ref={nodeRef}>Example Target</div> </Draggable> ); } export default MyDraggableComponent;
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]