Skip to content

Instantly share code, notes, and snippets.

@codemzy
Last active March 22, 2024 11:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save codemzy/99282424b323d0741e76e1cf7c62f2e4 to your computer and use it in GitHub Desktop.
Save codemzy/99282424b323d0741e76e1cf7c62f2e4 to your computer and use it in GitHub Desktop.
React Drag-and-Drop Component
import React from 'react';
// sub-components
import DragItem from './DragItem';
import DropZone from './DropZone';
import DropZones from './DropZones';
import DropGuide from './DropGuide';
// context for the drag
export const DragContext = React.createContext();
// drag context component
function Drag({ draggable = true, handleDrop, children }) {
const [dragItem, setDragItem] = React.useState(null); // the item id being dragged
const [dragType, setDragType] = React.useState(null); // if multiple types of drag item
const [isDragging, setIsDragging] = React.useState(null); // drag is happening
const [drop, setDrop] = React.useState(null); // the active dropzone
React.useEffect(() => {
if (dragItem) {
document.body.style.cursor = "grabbing"; // changes mouse to grabbing while dragging
} else {
document.body.style.cursor = "default"; // back to default when no dragItem
}
}, [dragItem]); // runs when dragItem state changes
const dragStart = function(e, dragId, dragType) {
e.stopPropagation();
e.dataTransfer.effectAllowed = 'move';
setDragItem(dragId);
setDragType(dragType);
};
const drag = function(e, dragId, dragType) {
e.stopPropagation();
setIsDragging(true);
};
const dragEnd = function() {
setDragItem(null);
setDragType(null);
setIsDragging(false);
setDrop(null);
};
const onDrop = function(e) {
e.preventDefault();
handleDrop({ dragItem, dragType, drop });
setDragItem(null);
setDragType(null);
setIsDragging(false);
setDrop(null);
};
return (
<DragContext.Provider value={{ draggable, dragItem, dragType, isDragging, dragStart, drag, dragEnd, drop, setDrop, onDrop }}>
{ typeof children === "function" ? children({ activeItem: dragItem, activeType: dragType, isDragging }) : children }
</DragContext.Provider>
);
};
// export Drag and assign sub-components
export default Object.assign(Drag, { DragItem, DropZone, DropZones, DropGuide });
import React from 'react';
// context
import DragContext from './Drag';
// a draggable item
function DragItem({ as, dragId, dragType, ...props }) {
const { draggable, dragStart, drag, dragEnd } = React.useContext(DragContext);
let Component = as || "div";
return <Component onDragStart={(e) => dragStart(e, dragId, dragType)} onDrag={drag} draggable={draggable} onDragEnd={dragEnd} {...props} />;
};
export default DragItem;
import React from 'react';
// context
import DragContext from './Drag';
// indicates where the drop will go when dragging over a dropzone
function DropGuide({ as, dropId, ...props }) {
const { drop } = React.useContext(DragContext);
let Component = as || "div";
return drop === dropId ? <Component {...props} /> : null;
};
export default DropGuide;
import React from 'react';
// context
import DragContext from './Drag';
// listens for drags over drop zones
function DropZone({ as, dropId, dropType, remember, style, children, ...props }) {
const { dragItem, dragType, setDrop, drop, onDrop } = React.useContext(DragContext);
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
return false;
};
function handleLeave() {
if (!remember) {
setDrop(null);
}
};
let Component = as || "div";
return (
<Component onDragEnter={(e) => dragItem && dropType === dragType && setDrop(dropId)} onDragOver={handleDragOver} onDrop={onDrop} style={{position: "relative", ...style}} {...props}>
{ children }
{ drop === dropId && <div style={{position: "absolute", inset: "0px"}} onDragLeave={handleLeave}></div> }
</Component>
);
};
export default DropZone;
import React from 'react';
import DropZone from './DropZone';
// context
import DragContext from './Drag';
// if we need multiple dropzones
function DropZones({ dropType, prevId, nextId, split = "y", remember, children, ...props }) {
const { dragType, isDragging } = React.useContext(DragContext);
return (
<div style={{position: "relative"}} {...props}>
{ children }
{ dragType === dropType && isDragging &&
<div style={{position: "absolute", inset: "0px", display: "flex", flexDirection: split === "x" ? "row" : "column" }}>
<DropZone dropId={prevId} style={{ width: "100%", height: "100%" }} dropType={dropType} remember={remember} />
<DropZone dropId={nextId} style={{ width: "100%", height: "100%" }} dropType={dropType} remember={remember} />
</div>
}
</div>
);
};
export default DropZones;
export * from './Drag';
export { default } from './Drag';
@codemzy
Copy link
Author

codemzy commented Oct 19, 2023

How to install

Download the Gist and rename the file "Drag", then drag it into your /components directory.

How to use

You can find more about how I built this component in Building a reusable drag and drop component with React with tips and examples for how to use it.

@nadeem977
Copy link

image

i using this code and I've get this error how can i fix it ??

@codemzy
Copy link
Author

codemzy commented Mar 22, 2024

image i using this code and I've get this error how can i fix it ??

It looks like the error says you need to end the file with .jsx file extension. I think with my setup (not using Vite) I can use .js extension and it knows if it is JSX but Vite does not allow usage of JSX syntax within .js files by default. Switching to Drag.jsx might fix it.

There is a thread here with more info on Vite config for JSX in js files: https://stackoverflow.com/questions/74620427/how-to-configure-vite-to-allow-jsx-syntax-in-js-files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment