Troubleshooting
Items jump right after mount
The layout was authored at one canvas width and the element measured another; the provider projected it. Expected. To avoid the visible move, save layouts at the rendered width, set responsive={false}, or hide the canvas until state.size is set. See server rendering.
Nothing drags
In order of likelihood:
- The element under the pointer is not inside an element with
data-gridla-drag-handle.GridItemsets it on itself unlessdraggable={false}, in which case only elements spread withdragHandlePropsstart a move. - The canvas is not the element the handlers are attached to. With a custom canvas, spread the object returned by
useGridInteraction(ref)on the same elementrefpoints to. - The pointer moved less than
dragThreshold(4px). A press that ends before that is a click and callsonItemClick. enabled={false}was passed toGridCanvas.- On touch, the page scrolled instead. The canvas needs
touch-action: none(set byGridCanvas; set it yourself on a custom canvas).
The move is rejected
moveItem returns accepted: false and the strategy rejected when no strategy applies: typically a locked item is in the way, the request would violate a minimum size, or the canvas is too full. During a drag the adapter keeps showing the last accepted preview, so the item appears to stick. Check the trace with onTrace to see which strategies were tried; loosen minW/minH, remove locked, or make the canvas scrollable.
onLayoutChange gives me coordinates in the wrong size
The layout is expressed in the canvas size it was rendered at. Project it to your authoring size with projectLayout(next, { width, height }) if you need one canonical size. See controlled state.
"this component must be rendered inside <GridProvider>"
A hook or component from gridla/react was used outside a provider. GridTransferScope is not a provider; each canvas still needs its own GridProvider inside the scope.
Transfers do not happen
- The providers are not inside a common
GridTransferScope. - The target's
acceptTransfersreturnedfalsefor the item. - The pointer is still inside the source canvas element. Siblings and ancestors only become targets after the pointer leaves the source; descendants can win immediately.
- The target could not place the item at all (which is rare, since the pointer form of
placeItemfalls through to an overlapping placement).
While a target previews the item, the source shows its base layout: no outline, no pushed siblings, only the active item following the pointer (flagged with data-gridla-transferring). The source preview returns on the first move back inside it.
Items overlap after a pointer placement
The pointer form of placeItem never rejects; as a last resort it returns pointer-overlap so a preview can be drawn. Check result.strategy (or findLayoutViolations(result.layout)) before committing.
Projection ignores my gaps
The chain engine preserves gaps that equal options.gap exactly. Pass the same gap you use for solving to projectLayout (the React provider does this automatically). Gaps of other sizes scale with the layout; applyGap normalizes a layout to a single gap first.
Snapping is too eager or too lazy
snapDistance defaults to 24px. Lower it or set snap: false for a particular call. Holding Ctrl / Cmd during a drag bypasses alignment snapping without changing the options.
A ghost item blocks nothing but cannot be dropped on either
That is what collision: 'ignore' means: other items pass through it, and it is excluded from overlap checks. If the ghost should reserve space, make it solid and locked instead.
React StrictMode double-invokes effects
Fine. The store is created once with useState, measurement is idempotent, and gesture state lives in refs.