Step 2: Drop a pin
One field for the pins, one click handler for dropping them — measured against the board's own box.
A pin board needs pins. Each one has a position and a label, and — since two people can
click different empty spots on the board at the same time — a field.map, the same field
Getting Started used for notes.
Edit the codec below. Replace the empty defineStateCodec({}) with:
export type Pin = {
x: number
y: number
label: string
}
export const codec = defineStateCodec({
// A per-KEY map: two people dropping different pins at once BOTH land.
pins: field.map<Pin>(),
})Then give Board a click handler that writes one. The board measures the click against
its own bounding box — never a parent's — so the pin lands exactly where you clicked:
const onBoardClick = (e: ReactMouseEvent<HTMLDivElement>) => {
if (!canEdit) return
const rect = boardRef.current!.getBoundingClientRect()
actions.map("pins").set(newId(), {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
label: "Pin",
})
}Edit and the example re-runs. Tab indents; press Escape to leave the editor.
You should see: the empty-state text is replaced by the board's grid, and clicking anywhere on it drops a 📍 exactly under your cursor.
Why e.target isn't checked yet
Click near an existing pin right now and you'll drop a second one practically on top of it — that's fine for a minute. Once pins can be dragged, in the next step, an unguarded click handler would also fire on the mouse-up that ends a drag. You'll fix both at once.