fix: Refactor useCanvasViewport to eliminate ESLint hook warnings

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 <noreply@anthropic.com>
This commit is contained in:
Jan-Henrik Bruhn 2025-12-26 21:49:58 +01:00
parent 7fd31d209c
commit e8f5c9085c

View file

@ -5,13 +5,7 @@
* Handles wheel zoom and button zoom operations * Handles wheel zoom and button zoom operations
*/ */
import { import { useState, useEffect, useCallback, type RefObject } from "react";
useState,
useEffect,
useCallback,
useRef,
type RefObject,
} from "react";
import type Konva from "konva"; import type Konva from "konva";
import type { PesPatternData } from "../formats/import/pesImporter"; import type { PesPatternData } from "../formats/import/pesImporter";
import type { MachineInfo } from "../types/machine"; import type { MachineInfo } from "../types/machine";
@ -34,8 +28,11 @@ export function useCanvasViewport({
const [stagePos, setStagePos] = useState({ x: 0, y: 0 }); const [stagePos, setStagePos] = useState({ x: 0, y: 0 });
const [stageScale, setStageScale] = useState(1); const [stageScale, setStageScale] = useState(1);
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }); const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
const initialScaleRef = useRef<number>(1); const [initialScale, setInitialScale] = useState(1);
const prevPesDataRef = useRef<PesPatternData | null>(null);
// Track the last processed pattern to detect changes during render
const [lastProcessedPattern, setLastProcessedPattern] =
useState<PesPatternData | null>(null);
// Track container size with ResizeObserver // Track container size with ResizeObserver
useEffect(() => { useEffect(() => {
@ -59,19 +56,14 @@ export function useCanvasViewport({
return () => resizeObserver.disconnect(); return () => resizeObserver.disconnect();
}, [containerRef]); }, [containerRef]);
// Calculate and store initial scale when pattern or hoop changes // Reset viewport when pattern changes (during render, not in effect)
useEffect(() => { // This follows the React-recommended pattern for deriving state from props
// Use whichever pattern is available (uploaded or original)
const currentPattern = uploadedPesData || pesData; const currentPattern = uploadedPesData || pesData;
if (!currentPattern || containerSize.width === 0) { if (
prevPesDataRef.current = null; currentPattern &&
return; currentPattern !== lastProcessedPattern &&
} containerSize.width > 0
) {
// Only recalculate if pattern changed
if (prevPesDataRef.current !== currentPattern) {
prevPesDataRef.current = currentPattern;
const { bounds } = currentPattern; const { bounds } = currentPattern;
const viewWidth = machineInfo const viewWidth = machineInfo
? machineInfo.maxWidth ? machineInfo.maxWidth
@ -80,20 +72,20 @@ export function useCanvasViewport({
? machineInfo.maxHeight ? machineInfo.maxHeight
: bounds.maxY - bounds.minY; : bounds.maxY - bounds.minY;
const initialScale = calculateInitialScale( const newInitialScale = calculateInitialScale(
containerSize.width, containerSize.width,
containerSize.height, containerSize.height,
viewWidth, viewWidth,
viewHeight, viewHeight,
); );
initialScaleRef.current = initialScale;
// Reset view when pattern changes // Update state during render when pattern changes
// eslint-disable-next-line react-hooks/set-state-in-effect // This is the recommended React pattern for resetting state based on props
setStageScale(initialScale); setLastProcessedPattern(currentPattern);
setInitialScale(newInitialScale);
setStageScale(newInitialScale);
setStagePos({ x: containerSize.width / 2, y: containerSize.height / 2 }); setStagePos({ x: containerSize.width / 2, y: containerSize.height / 2 });
} }
}, [pesData, uploadedPesData, machineInfo, containerSize]);
// Wheel zoom handler // Wheel zoom handler
const handleWheel = useCallback((e: Konva.KonvaEventObject<WheelEvent>) => { const handleWheel = useCallback((e: Konva.KonvaEventObject<WheelEvent>) => {
@ -159,10 +151,9 @@ export function useCanvasViewport({
}, [containerSize]); }, [containerSize]);
const handleZoomReset = useCallback(() => { const handleZoomReset = useCallback(() => {
const initialScale = initialScaleRef.current;
setStageScale(initialScale); setStageScale(initialScale);
setStagePos({ x: containerSize.width / 2, y: containerSize.height / 2 }); setStagePos({ x: containerSize.width / 2, y: containerSize.height / 2 });
}, [containerSize]); }, [initialScale, containerSize]);
return { return {
// State // State