mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13:41 +00:00
fix: Resolve state-during-render anti-pattern in usePatternTransform
Moved state synchronization logic from render phase to useEffect hooks to prevent potential infinite loops and unpredictable behavior. Implemented ref-based previous value tracking to detect genuine parent prop changes without causing cascading renders. Changes: - Replaced direct setState calls during render with properly structured useEffect hooks - Added prevOffsetRef and prevRotationRef to track previous prop values - Documented the "partially controlled" pattern needed for Konva drag interactions - Added justified ESLint disable comments for legitimate setState-in-effect usage This fixes a critical React anti-pattern that could cause performance issues and render loops. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7fd31d209c
commit
47e32ef83d
1 changed files with 34 additions and 15 deletions
|
|
@ -39,22 +39,41 @@ export function usePatternTransform({
|
||||||
const patternGroupRef = useRef<Konva.Group | null>(null);
|
const patternGroupRef = useRef<Konva.Group | null>(null);
|
||||||
const transformerRef = useRef<Konva.Transformer | null>(null);
|
const transformerRef = useRef<Konva.Transformer | null>(null);
|
||||||
|
|
||||||
// Update pattern offset when initialPatternOffset changes
|
// Track previous prop values to detect external changes
|
||||||
if (
|
const prevOffsetRef = useRef(initialPatternOffset);
|
||||||
initialPatternOffset &&
|
const prevRotationRef = useRef(initialPatternRotation);
|
||||||
(localPatternOffset.x !== initialPatternOffset.x ||
|
|
||||||
localPatternOffset.y !== initialPatternOffset.y)
|
|
||||||
) {
|
|
||||||
setLocalPatternOffset(initialPatternOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update pattern rotation when initialPatternRotation changes
|
// Sync local state with parent props when they change externally
|
||||||
if (
|
// This implements a "partially controlled" pattern needed for Konva drag interactions:
|
||||||
initialPatternRotation !== undefined &&
|
// - Local state enables optimistic updates during drag/transform (immediate visual feedback)
|
||||||
localPatternRotation !== initialPatternRotation
|
// - Parent props sync when external changes occur (e.g., pattern upload resets position)
|
||||||
) {
|
// - Previous value refs prevent sync loops by only updating when props genuinely change
|
||||||
setLocalPatternRotation(initialPatternRotation);
|
useEffect(() => {
|
||||||
}
|
if (
|
||||||
|
initialPatternOffset &&
|
||||||
|
(prevOffsetRef.current.x !== initialPatternOffset.x ||
|
||||||
|
prevOffsetRef.current.y !== initialPatternOffset.y)
|
||||||
|
) {
|
||||||
|
// This setState in effect is intentional and safe: it only runs when the parent
|
||||||
|
// prop changes, not in response to our own local updates
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
|
setLocalPatternOffset(initialPatternOffset);
|
||||||
|
prevOffsetRef.current = initialPatternOffset;
|
||||||
|
}
|
||||||
|
}, [initialPatternOffset]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
initialPatternRotation !== undefined &&
|
||||||
|
prevRotationRef.current !== initialPatternRotation
|
||||||
|
) {
|
||||||
|
// This setState in effect is intentional and safe: it only runs when the parent
|
||||||
|
// prop changes, not in response to our own local updates
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
|
setLocalPatternRotation(initialPatternRotation);
|
||||||
|
prevRotationRef.current = initialPatternRotation;
|
||||||
|
}
|
||||||
|
}, [initialPatternRotation]);
|
||||||
|
|
||||||
// Attach/detach transformer based on state
|
// Attach/detach transformer based on state
|
||||||
const attachTransformer = useCallback(() => {
|
const attachTransformer = useCallback(() => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue