fix: stale closure bug preventing canvas tools from creating elements
- CanvasArea: add useEffect to refresh InteractionEngine callbacks when props change - App.tsx: handleElementCreated/Deleted/Modified now use functional setElements updates - Root cause: CanvasArea init useEffect had empty deps, capturing stale callbacks at mount - handleElementCreated used [...elements, el] with stale elements=[] closure - Each new element replaced all previous ones instead of appending - All 343 tests pass, tsc clean
This commit is contained in:
+25
-19
@@ -293,11 +293,13 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
}, [collab.elements, collab.layers, collab.blocks, collab.status]);
|
||||
|
||||
const handleElementCreated = useCallback((el: CADElement) => {
|
||||
const newElements = [...elements, el];
|
||||
historyManagerRef.current.pushSnapshot({
|
||||
elements: newElements, layers, blocks, groups, bgConfig,
|
||||
}, 'Element erstellt');
|
||||
setElements(newElements);
|
||||
setElements((prev) => {
|
||||
const newElements = [...prev, el];
|
||||
historyManagerRef.current.pushSnapshot({
|
||||
elements: newElements, layers, blocks, groups, bgConfig,
|
||||
}, 'Element erstellt');
|
||||
return newElements;
|
||||
});
|
||||
syncHistory();
|
||||
// Push to Yjs CRDT for real-time sync
|
||||
collab.setElement(el);
|
||||
@@ -311,14 +313,16 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
setSavedStatus('Fehler beim Speichern');
|
||||
});
|
||||
}
|
||||
}, [elements, layers, blocks, groups, bgConfig, syncHistory, drawingId, token, collab]);
|
||||
}, [layers, blocks, groups, bgConfig, syncHistory, drawingId, token, collab]);
|
||||
|
||||
const handleElementsDeleted = useCallback((ids: string[]) => {
|
||||
const newElements = elements.filter((e) => !ids.includes(e.id));
|
||||
historyManagerRef.current.pushSnapshot({
|
||||
elements: newElements, layers, blocks, groups, bgConfig,
|
||||
}, `${ids.length} Element(e) gelöscht`);
|
||||
setElements(newElements);
|
||||
setElements((prev) => {
|
||||
const newElements = prev.filter((e) => !ids.includes(e.id));
|
||||
historyManagerRef.current.pushSnapshot({
|
||||
elements: newElements, layers, blocks, groups, bgConfig,
|
||||
}, `${ids.length} Element(e) gelöscht`);
|
||||
return newElements;
|
||||
});
|
||||
syncHistory();
|
||||
// Push deletions to Yjs CRDT for real-time sync
|
||||
ids.forEach(id => collab.deleteElement(id));
|
||||
@@ -335,14 +339,16 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
}, [elements, layers, blocks, groups, bgConfig, syncHistory, token, collab]);
|
||||
|
||||
const handleElementsModified = useCallback((modified: CADElement[]) => {
|
||||
const newElements = elements.map((e) => {
|
||||
const found = modified.find((m) => m.id === e.id);
|
||||
return found ?? e;
|
||||
setElements((prev) => {
|
||||
const newElements = prev.map((e) => {
|
||||
const found = modified.find((m) => m.id === e.id);
|
||||
return found ?? e;
|
||||
});
|
||||
historyManagerRef.current.pushSnapshot({
|
||||
elements: newElements, layers, blocks, groups, bgConfig,
|
||||
}, `${modified.length} Element(e) geändert`);
|
||||
return newElements;
|
||||
});
|
||||
historyManagerRef.current.pushSnapshot({
|
||||
elements: newElements, layers, blocks, groups, bgConfig,
|
||||
}, `${modified.length} Element(e) geändert`);
|
||||
setElements(newElements);
|
||||
syncHistory();
|
||||
// Push modifications to Yjs CRDT for real-time sync
|
||||
modified.forEach(el => collab.setElement(el));
|
||||
@@ -356,7 +362,7 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
setSavedStatus('Fehler beim Speichern');
|
||||
});
|
||||
}
|
||||
}, [elements, layers, blocks, groups, bgConfig, syncHistory, token, collab]);
|
||||
}, [layers, blocks, groups, bgConfig, syncHistory, token, collab]);
|
||||
|
||||
const lastCursorUpdateRef = useRef(0);
|
||||
const handleCursorMoved = useCallback((x: number, y: number) => {
|
||||
|
||||
@@ -72,6 +72,23 @@ const CanvasArea: React.FC<CanvasAreaProps> = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Update callbacks when they change (avoids stale closures)
|
||||
useEffect(() => {
|
||||
const interaction = interactionRef.current;
|
||||
if (!interaction) return;
|
||||
interaction.setCallbacks({
|
||||
onElementCreated,
|
||||
onElementsDeleted,
|
||||
onElementsModified,
|
||||
onCursorMoved,
|
||||
onToolStateChanged,
|
||||
onTextEdit,
|
||||
onCommandTrigger,
|
||||
onSelectionChange,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [onElementCreated, onElementsDeleted, onElementsModified, onCursorMoved, onToolStateChanged, onTextEdit, onCommandTrigger, onSelectionChange]);
|
||||
|
||||
// Resize canvas to fill container
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
|
||||
Reference in New Issue
Block a user