1- import { useState , useEffect } from "react" ;
1+ import { useState , useEffect , useRef } from "react" ;
22import { getFontFamily } from "../../lib/fontCache" ;
33import { useFontCacheVersion } from "../../hooks/useFontCacheVersion" ;
44import {
@@ -246,11 +246,18 @@ function ImageObject({
246246 const y = offsetY + dotsToPx ( obj . y , scale , dpmm ) ;
247247
248248 const [ htmlImg , setHtmlImg ] = useState < HTMLImageElement | null > ( null ) ;
249+ // Reset the cached HTMLImageElement during render when the source changes,
250+ // instead of inside an effect. The "set state during render on prop change"
251+ // pattern is the official React workaround for what would otherwise be a
252+ // setState-in-effect anti-pattern. The next render observes prevDataUrl
253+ // already updated, so this does not loop.
254+ const prevDataUrlRef = useRef < string | undefined > ( cached ?. dataUrl ) ;
255+ if ( prevDataUrlRef . current !== cached ?. dataUrl ) {
256+ prevDataUrlRef . current = cached ?. dataUrl ;
257+ setHtmlImg ( null ) ;
258+ }
249259 useEffect ( ( ) => {
250- if ( ! cached ) {
251- setHtmlImg ( null ) ; // eslint-disable-line react-hooks/set-state-in-effect
252- return ;
253- }
260+ if ( ! cached ) return ;
254261 let active = true ;
255262 const img = new window . Image ( ) ;
256263 img . src = cached . dataUrl ;
@@ -260,7 +267,7 @@ function ImageObject({
260267 return ( ) => {
261268 active = false ;
262269 } ;
263- } , [ cached ?. dataUrl , cached ] ) ;
270+ } , [ cached ] ) ;
264271
265272 const handleDragMove = ( e : Konva . KonvaEventObject < DragEvent > ) => {
266273 onChange ( {
@@ -376,7 +383,7 @@ function KonvaObjectInner({
376383 let displayY = obj . y ;
377384 if ( obj . positionType === "FT" ) {
378385 if ( obj . type === "text" || obj . type === "serial" ) {
379- const p = obj . props as { fontHeight : number ; rotation : string } ;
386+ const p = obj . props ;
380387 // ^FT places the origin at the baseline of the first character.
381388 // The Konva anchor point after rotation sits at a different corner
382389 // of the visual bounding box than the ZPL FT baseline origin:
@@ -402,7 +409,7 @@ function KonvaObjectInner({
402409 // Konva rotates text around its top-left corner, but ZPL's ^FO anchor
403410 // shifts with rotation. 15 dots is an empirically determined fixed offset.
404411 if ( obj . type === "text" || obj . type === "serial" ) {
405- const p = obj . props as { fontHeight : number ; rotation : string } ;
412+ const p = obj . props ;
406413 const ROTATION_OFFSET = 15 ; // dots — empirical canvas/ZPL alignment correction
407414 if ( p . rotation === "I" ) {
408415 displayY -= ROTATION_OFFSET ;
@@ -435,7 +442,7 @@ function KonvaObjectInner({
435442 let finalY = pxToDots ( e . target . y ( ) - offsetY , scale , dpmm ) ;
436443
437444 if ( obj . type === "text" || obj . type === "serial" ) {
438- const p = obj . props as { fontHeight : number ; rotation : string } ;
445+ const p = obj . props ;
439446 const ROTATION_OFFSET = 15 ;
440447 if ( p . rotation === "I" ) {
441448 finalY += ROTATION_OFFSET ;
@@ -451,7 +458,7 @@ function KonvaObjectInner({
451458 // instead of the ZPL baseline coordinate, causing a vertical jump on re-render.
452459 if ( obj . positionType === "FT" ) {
453460 if ( obj . type === "text" || obj . type === "serial" ) {
454- const p = obj . props as { fontHeight : number ; rotation : string } ;
461+ const p = obj . props ;
455462 const renderedH = p . fontHeight / 1.3 ;
456463 if ( p . rotation === "N" ) {
457464 finalY += p . fontHeight ;
0 commit comments