Project 5
Every project so far has worked with a flat array — a list of todos, a list of notes. A Kanban board's state is one level more complex: an object where each key holds its own array (one per column), and the core interaction — dragging a card from one column to another — means immutably removing an item from one array and adding it to a different one, in the same update. This project also introduces the browser's native drag-and-drop events, which React doesn't wrap in anything special — they're used directly, the same way onClick always has been.
draggable on the card itself; onDragStart (fires when dragging begins — store which card and which column it came from); onDragOver on each column (must call e.preventDefault(), or the column will never accept a drop); and onDrop on each column (where the actual state update happens, moving the card into that column).
Requirements
- At least three columns (e.g. "To Do," "In Progress," "Done"), each showing its own list of cards.
- A way to add a new card with some text, landing in a specific column.
- Dragging a card from one column and dropping it on another moves it there — removed from the original column's list, added to the new one.
- A card can be deleted from whichever column it's currently in.
- The column currently being dragged over should look visually different (e.g. a highlighted border) while a card is hovering over it.
- Dropping a card back into its original column should not duplicate it or lose it.
Suggested Component Breakdown
A reasonable shape for the state itself: { todo: [{ id, text }, ...], inProgress: [...], done: [...] }. KanbanBoard owns this whole object — exactly the "lowest common ancestor" reasoning from Fundamentals Chapter 10, since moving a card between columns is fundamentally a change that affects two columns (siblings) at once, which only their shared parent can coordinate.
A Reasonable Build Order
- Hardcode the columns object with a few sample cards in each, and get all three columns rendering their cards correctly with
.map()before touching drag-and-drop at all. - Move the hardcoded object into
useState, and buildAddCardFormso new cards can be added to a chosen column's array. - Add
draggableandonDragStarttoCard, storing the card's id and its current column name (state, a ref, ore.dataTransferall work) somewhereonDropcan read it from later. - Add
onDragOver(withe.preventDefault()) andonDroptoColumn. On drop, build a new columns object: filter the card out of its source column's array, and add it to the destination column's array. - Add the drag-over highlight — a small piece of state in each
Column(or lifted up, tracking which column id is currently being dragged over) toggled byonDragEnter/onDragLeave. - Add the delete button per card last, once the drag-and-drop flow is solid.
e.preventDefault() inside onDragOver is the single most common reason a drop silently does nothing — the browser's default behavior for drag-over is to reject the drop entirely unless that default is explicitly cancelled. Separately, building the new columns object by mutating the existing arrays in place (e.g. .push() or .splice() directly on a state array) won't reliably trigger a re-render — always build new arrays (.filter(), spread) and a new outer object, the same immutability rule from Fundamentals Chapter 3, just one level deeper this time.
- Support reordering cards within the same column, not just moving between columns.
- Persist the whole board to
localStorage, same pattern as Project 3's notes. - Swap the native drag-and-drop API for a dedicated library (e.g.
@dnd-kit/core), which adds proper keyboard/accessibility support that the native API lacks. - Let columns themselves be added, renamed, or removed, rather than being fixed at three.
- Add a card-detail view (click to expand a card into a longer description, due date, etc.).