React Draggable Component
react-draggable is a React component library that makes HTML elements draggable within a web application. The current stable version is 4.5.0, with minor bugfix releases occurring periodically within a major version, and major versions released less frequently, indicating a stable and mature project. A key differentiator is that it doesn't create an additional wrapper element in the DOM, directly extending the wrapped element with event handlers and styles. It uses CSS Transforms for movement, allowing elements to be dragged regardless of their initial positioning and enabling movement between drags without issue. The library offers both a higher-order `Draggable` component for simple use cases and a lower-level `DraggableCore` for more fine-grained control over drag interactions, supporting both controlled and uncontrolled component patterns. It also ships with TypeScript types.
Common errors
-
Cannot read properties of undefined (reading 'clientX') or cannot access clientX of undefined
cause This typically occurs on some touch-enabled platforms due to issues with event object normalization in older versions.fixUpgrade `react-draggable` to version 2.0.2 or newer, as this version specifically addressed bugs related to `clientX` being undefined on certain touch platforms. -
Error: Argument 1 of Window.getComputedStyle does not implement interface Element.
cause An invalid argument was passed to `window.getComputedStyle`, often related to how SVG elements or non-standard DOM nodes were being handled.fixUpdate `react-draggable` to version 2.2.1 or newer. This version includes a bugfix for `getComputedStyle` errors. -
Object doesn't support property or method 'constructor' in IE10
cause An incompatibility with JavaScript constructors in Internet Explorer 10 was causing errors during component initialization.fixUpgrade `react-draggable` to version 2.0.1 or newer. This version contains a specific fix for the IE10 constructor bug.
Warnings
- breaking Version 2.0.0 introduced significant breaking changes, specifically affecting event callbacks and the usage of `position` and `defaultPosition` props. Previous implementations will need to be updated to match the new API.
- gotcha If the element you are wrapping with Draggable already has CSS Transforms applied, Draggable will overwrite them. This is because Draggable uses CSS Transforms for its positioning.
- breaking Version 2.1.0 fixed an issue where `handle` or `cancel` selectors might be improperly missed if the event originated from a child of the handle or cancel element. This change, while a fix, might alter behavior for existing workarounds.
- gotcha Dragging on iDevices could cause the entire window to scroll instead of just the draggable element.
Install
-
npm install react-draggable -
yarn add react-draggable -
pnpm add react-draggable
Imports
- Draggable
const Draggable = require('react-draggable');import Draggable from 'react-draggable';
- DraggableCore
const DraggableCore = require('react-draggable').DraggableCore;import { DraggableCore } from 'react-draggable'; - DraggableEvent, DraggableData
import type { DraggableEvent, DraggableData } from 'react-draggable';
Quickstart
import React from 'react';
import ReactDOM from 'react-dom';
import Draggable, { DraggableCore } from 'react-draggable';
interface AppState {
activeDrags: number;
deltaPosition: { x: number; y: number; };
controlledPosition: { x: number; y: number; };
}
class App extends React.Component<{}, AppState> {
state = {
activeDrags: 0,
deltaPosition: { x: 0, y: 0 },
controlledPosition: { x: -400, y: 200 }
};
handleDrag = (e: any, ui: any) => {
const { x, y } = this.state.deltaPosition;
this.setState({
deltaPosition: { x: x + ui.deltaX, y: y + ui.deltaY }
});
};
onStart = () => {
this.setState({ activeDrags: ++this.state.activeDrags });
};
onStop = () => {
this.setState({ activeDrags: --this.state.activeDrags });
};
// For controlled component
adjustXPos = (e: any) => {
e.preventDefault();
e.stopPropagation();
const { x, y } = this.state.controlledPosition;
this.setState({ controlledPosition: { x: x - 10, y } });
};
adjustYPos = (e: any) => {
e.preventDefault();
e.stopPropagation();
const { x, y } = this.state.controlledPosition;
this.setState({ controlledPosition: { x, y: y - 10 } });
};
onControlledDrag = (e: any, ui: any) => {
const { x, y } = this.state.controlledPosition;
this.setState({
controlledPosition: { x: x + ui.deltaX, y: y + ui.deltaY }
});
};
onControlledDragStop = (e: any, ui: any) => {
this.onStop();
this.onControlledDrag(e, ui);
};
render() {
const dragHandlers = { onStart: this.onStart, onStop: this.onStop };
const { deltaPosition, controlledPosition } = this.state;
return (
<div>
<h1>React Draggable Demo</h1>
<Draggable {...dragHandlers}>
<div className="box">
<div>I can be dragged!</div>
</div>
</Draggable>
<Draggable axis="x" {...dragHandlers}>
<div className="box">
<div>Only horizontal dragging</div>
</div>
</Draggable>
<Draggable axis="y" {...dragHandlers}>
<div className="box">
<div>Only vertical dragging</div>
</div>
</Draggable>
<Draggable handle=".handle" {...dragHandlers}>
<div className="box">
<div className="handle">Drag from here</div>
<div>Clicking here won't move me</div>
</div>
</Draggable>
<Draggable cancel=".no-drag" {...dragHandlers}>
<div className="box">
<div className="no-drag">Clicking here won't move me</div>
<div>Drag from here</div>
</div>
</Draggable>
<Draggable grid={[25, 25]} {...dragHandlers}>
<div className="box">
<div>I snap to a 25 x 25 grid</div>
</div>
</Draggable>
<Draggable scale={0.5} {...dragHandlers}>
<div className="box">
<div>I am 50% scale</div>
</div>
</Draggable>
<Draggable
defaultPosition={{x: 0, y: 0}}
position={controlledPosition}
onDrag={this.onControlledDrag}
onStop={this.onControlledDragStop}
>
<div className="box">
I am a controlled component.
<br />
My position is ({controlledPosition.x}, {controlledPosition.y})
<br />
<button onClick={this.adjustXPos}>Adjust x (-10)</button>
<button onClick={this.adjustYPos}>Adjust y (-10)</button>
</div>
</Draggable>
<h2>DraggableCore</h2>
<DraggableCore {...dragHandlers}>
<div className="box no-cursor">
I'm a DraggableCore.<br />
I only fire events.<br />
My position is ({deltaPosition.x}, {deltaPosition.y})
</div>
</DraggableCore>
</div>
);
}
}
// Basic CSS for demonstration
const styleSheet = document.createElement('style');
styleSheet.innerHTML = `
.box {
background: #eee;
border: 1px solid #999;
border-radius: 3px;
width: 180px;
height: 180px;
margin: 10px;
padding: 10px;
float: left;
cursor: grab;
font-family: monospace;
font-size: 14px;
text-align: center;
}
.box .handle {
cursor: grab;
background-color: #ccc;
padding: 5px;
margin-bottom: 5px;
}
.box .no-drag {
cursor: not-allowed;
background-color: #fdd;
padding: 5px;
margin-bottom: 5px;
}
.box.no-cursor {
cursor: default;
}
`;
document.head.appendChild(styleSheet);
ReactDOM.render(<App />, document.getElementById('root'));