From e8f5c9085cd3404916070e86bafaa395ad7c5fbd Mon Sep 17 00:00:00 2001 From: Jan-Henrik Bruhn Date: Fri, 26 Dec 2025 21:49:58 +0100 Subject: [PATCH] fix: Refactor useCanvasViewport to eliminate ESLint hook warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed disabled ESLint rules by refactoring state management to follow React best practices. Changes include: - Replaced setState-in-effect pattern with state-during-render pattern for viewport resets - Changed from ref-based to state-based storage for initialScale to avoid ref updates during render - Implemented React-recommended pattern for deriving state from props (similar to getDerivedStateFromProps) - Properly track pattern changes in state to detect when viewport should reset This eliminates the need for ESLint disable comments while maintaining the same functionality. The viewport now correctly resets when patterns change, using a pattern that React explicitly recommends for this use case. Fixes #30 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/hooks/useCanvasViewport.ts | 79 +++++++++++++++------------------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/src/hooks/useCanvasViewport.ts b/src/hooks/useCanvasViewport.ts index b300945..c8e4727 100644 --- a/src/hooks/useCanvasViewport.ts +++ b/src/hooks/useCanvasViewport.ts @@ -5,13 +5,7 @@ * Handles wheel zoom and button zoom operations */ -import { - useState, - useEffect, - useCallback, - useRef, - type RefObject, -} from "react"; +import { useState, useEffect, useCallback, type RefObject } from "react"; import type Konva from "konva"; import type { PesPatternData } from "../formats/import/pesImporter"; import type { MachineInfo } from "../types/machine"; @@ -34,8 +28,11 @@ export function useCanvasViewport({ const [stagePos, setStagePos] = useState({ x: 0, y: 0 }); const [stageScale, setStageScale] = useState(1); const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }); - const initialScaleRef = useRef(1); - const prevPesDataRef = useRef(null); + const [initialScale, setInitialScale] = useState(1); + + // Track the last processed pattern to detect changes during render + const [lastProcessedPattern, setLastProcessedPattern] = + useState(null); // Track container size with ResizeObserver useEffect(() => { @@ -59,41 +56,36 @@ export function useCanvasViewport({ return () => resizeObserver.disconnect(); }, [containerRef]); - // Calculate and store initial scale when pattern or hoop changes - useEffect(() => { - // Use whichever pattern is available (uploaded or original) - const currentPattern = uploadedPesData || pesData; - if (!currentPattern || containerSize.width === 0) { - prevPesDataRef.current = null; - return; - } + // Reset viewport when pattern changes (during render, not in effect) + // This follows the React-recommended pattern for deriving state from props + const currentPattern = uploadedPesData || pesData; + if ( + currentPattern && + currentPattern !== lastProcessedPattern && + containerSize.width > 0 + ) { + const { bounds } = currentPattern; + const viewWidth = machineInfo + ? machineInfo.maxWidth + : bounds.maxX - bounds.minX; + const viewHeight = machineInfo + ? machineInfo.maxHeight + : bounds.maxY - bounds.minY; - // Only recalculate if pattern changed - if (prevPesDataRef.current !== currentPattern) { - prevPesDataRef.current = currentPattern; + const newInitialScale = calculateInitialScale( + containerSize.width, + containerSize.height, + viewWidth, + viewHeight, + ); - const { bounds } = currentPattern; - const viewWidth = machineInfo - ? machineInfo.maxWidth - : bounds.maxX - bounds.minX; - const viewHeight = machineInfo - ? machineInfo.maxHeight - : bounds.maxY - bounds.minY; - - const initialScale = calculateInitialScale( - containerSize.width, - containerSize.height, - viewWidth, - viewHeight, - ); - initialScaleRef.current = initialScale; - - // Reset view when pattern changes - // eslint-disable-next-line react-hooks/set-state-in-effect - setStageScale(initialScale); - setStagePos({ x: containerSize.width / 2, y: containerSize.height / 2 }); - } - }, [pesData, uploadedPesData, machineInfo, containerSize]); + // Update state during render when pattern changes + // This is the recommended React pattern for resetting state based on props + setLastProcessedPattern(currentPattern); + setInitialScale(newInitialScale); + setStageScale(newInitialScale); + setStagePos({ x: containerSize.width / 2, y: containerSize.height / 2 }); + } // Wheel zoom handler const handleWheel = useCallback((e: Konva.KonvaEventObject) => { @@ -159,10 +151,9 @@ export function useCanvasViewport({ }, [containerSize]); const handleZoomReset = useCallback(() => { - const initialScale = initialScaleRef.current; setStageScale(initialScale); setStagePos({ x: containerSize.width / 2, y: containerSize.height / 2 }); - }, [containerSize]); + }, [initialScale, containerSize]); return { // State