anvil: stale closure in click handler captures drag state at mount time - canvas clicks not suppressed during active widget drag
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
- User opens the Anvil editor. At mount, isDragging = false.
- User starts dragging a widget. Redux updates isDragging = true.
- useClickToClearSelections returns a new function with the updated value.
- useCallback creates a new handleOnClickCapture reference.
- The useEffect with [] deps does NOT re-run - the old handler (with isDragging = false) remains registered.
- User clicks the canvas during the drag.
- The stale handler sees isDragging = false, passes the guard, and calls goToWidgetAdd(), ocusWidget(), and showPropertyPane().
- 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.
Source: appsmithorg/appsmith