Problem Statement
Build a small kanban board with Todo, In Progress, and Completed columns where tasks can be dragged from one column to another.
Functional Requirements
- Columns: Render one column per key in the
initialDataprop, with its tasks listed under the column title. - Drag a task: Every task is
draggable, and the dragged task fades to 50% opacity until the drag ends. - Drop on a column: Dropping a task on a column removes it from its source column and adds it to the end of the target column.
- Ignore other drops: Dropping something that is not a task, like a file or selected text, changes nothing.
Technical Constraints
- Use the native HTML drag and drop events (
dragstart,dragend,dragover,drop). No library. - Track the dragged task and its source column in refs, and update the board with an immutable
setData.
Component Overview
This is a React component implementing drag and drop functionality, typically used for kanban boards or task management interfaces.
Key Concepts
State and Refs
const [data, setData] = useState(initialData);
const dragItem = useRef();
const dragContainer = useRef();data: Stores the current state of items in different containersdragItem: Ref to track the item being draggeddragContainer: Ref to track the source container of dragged item
Event Handlers
1. handleDragStart
const handleDragStart = (e, item, container) => {
dragItem.current = item;
dragContainer.current = container;
e.target.style.opacity = 0.5;
};- Triggered when user starts dragging an item
- Stores the dragged item and its container in refs
- Reduces opacity of dragged element for visual feedback
2. handleDragEnd
const handleDragEnd = (e) => {
e.target.style.opacity = 1;
dragItem.current = null;
dragContainer.current = null;
};- Triggered when drag operation ends
- Restores opacity of dragged element
- Clears the refs so an old drag cannot be replayed by a later drop
3. handleDrop
const handleDrop = (e, targetContainer) => {
const item = dragItem.current;
const sourceContainer = dragContainer.current;
// Ignore drops that did not start on one of our tasks (files, text)
if (!item) return;
dragItem.current = null;
dragContainer.current = null;
setData((prev) => {
const newData = { ...prev };
// Remove item from source container
newData[sourceContainer] = newData[sourceContainer].filter(
(i) => i !== item
);
// Add item to target container
newData[targetContainer] = [...newData[targetContainer], item];
return newData;
});
};- Handles the drop event
- Does nothing if no task drag is in progress
- Moves item from source to target container
- Updates state with new arrangement
4. handleDragOver
const handleDragOver = (e) => {
e.preventDefault();
};- Required to enable dropping
- Prevents default browser behavior
Render Logic
Container Structure
<div className="board">
{Object.keys(data).map((container, index) => (
<div
key={index}
className="column"
onDrop={(e) => handleDrop(e, container)}
onDragOver={handleDragOver}
>- Maps through containers
- Sets up drop zone for each container
Draggable Items
<div
key={idx}
className="task"
draggable
onDragStart={(e) => handleDragStart(e, item, container)}
onDragEnd={(e) => handleDragEnd(e)}
>
{item}
</div>- Renders individual draggable items
- Attaches drag event handlers
Focus Points
-
HTML5 Drag and Drop API
- Native browser API
- Key events: dragstart, dragend, dragover, drop
-
React State Management
- Immutable state updates
- Using spread operator for object/array manipulation
-
React Refs
- Purpose of useRef
- Maintaining values between renders
-
Event Handling
- preventDefault() usage
- Event parameter in handlers