Skip to content

anvil: stale closure in click handler captures drag state at mount time - canvas clicks not suppressed during active widget drag #42118

Description

@harsh4vardhan

Bug Description

AnvilEditorCanvas registers a click handler via �ddEventListener in a useEffect with an empty dependency array ([]). The handler closes over drag/resize state values from Redux. Because the effect never re-runs, the handler always uses the state captured at mount time. When drag state changes, the click guard reads stale values and fails to suppress canvas clicks during active drag operations.

Affected files

�pp/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx, lines 47-53:

ypescript useEffect(() => { canvasRef.current?.addEventListener("click", handleOnClickCapture); return () => { canvasRef.current?.removeEventListener("click", handleOnClickCapture); }; }, []); // empty deps - handler never updated

�pp/client/src/layoutSystems/anvil/editor/canvas/hooks/useClickToClearSelections.ts, lines 18-31:

` ypescript
const isDragging = useSelector(state => state.ui.widgetDragResize.isDragging);
const isCanvasResizing = useSelector(state => state.ui.widgetDragResize.isAutoCanvasResizing);
const isDistributingSpace = useSelector(getAnvilSpaceDistributionStatus);

return (e) => {
if (!(isDragging || isCanvasResizing || isDistributingSpace)) { // stale values
goToWidgetAdd();
focusWidget(widgetId);
showPropertyPane();
e.preventDefault();
}
};
`

Failure scenario

  1. User opens the Anvil editor. At mount, isDragging = false.
  2. User starts dragging a widget. Redux updates isDragging = true.
  3. useClickToClearSelections returns a new function with the updated value.
  4. useCallback creates a new handleOnClickCapture reference.
  5. The useEffect with [] deps does NOT re-run - the old handler (with isDragging = false) remains registered.
  6. User clicks the canvas during the drag.
  7. The stale handler sees isDragging = false, passes the guard, and calls goToWidgetAdd(), ocusWidget(), and showPropertyPane().
  8. Widget selection is disrupted mid-drag. The property pane jumps to a different widget. The drag may be cancelled unexpectedly.

Fix

Add handleOnClickCapture to the dependency array, or use a ref to hold the latest handler:

` ypescript
const handleOnClickCaptureRef = useRef(handleOnClickCapture);
useEffect(() => { handleOnClickCaptureRef.current = handleOnClickCapture; });

useEffect(() => {
const handler = (e: MouseEvent) => handleOnClickCaptureRef.current(e);
canvasRef.current?.addEventListener("click", handler);
return () => { canvasRef.current?.removeEventListener("click", handler); };
}, []);
`

Environment

Appsmith elease branch (2026-08-13), React 18.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions