fix: prevent infinite render loop in zoom overlay callbacks

Use useRef to store onStatsUpdate/onLoadingChange/onActiveMotionChange
callbacks so useEffect deps don't include the callback references.
Inline arrow functions in .map() change identity every render, causing
the previous useEffect([stats, onCallback]) to re-fire on each parent
re-render, triggering another setState → re-render → infinite loop.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
This commit is contained in:
Claude 2026-03-16 09:54:16 +00:00
parent 9307272007
commit 89864e364d
No known key found for this signature in database

View File

@ -111,9 +111,16 @@ export default function LivePlayer({
droppedFrameRate: 0, // percentage
});
const onStatsUpdateRef = useRef(onStatsUpdate);
const onLoadingChangeRef = useRef(onLoadingChange);
const onActiveMotionChangeRef = useRef(onActiveMotionChange);
onStatsUpdateRef.current = onStatsUpdate;
onLoadingChangeRef.current = onLoadingChange;
onActiveMotionChangeRef.current = onActiveMotionChange;
useEffect(() => {
onStatsUpdate?.(stats);
}, [stats, onStatsUpdate]);
onStatsUpdateRef.current?.(stats);
}, [stats]);
// camera activity
@ -285,40 +292,24 @@ export default function LivePlayer({
}, [liveReady, isReEnabling]);
useEffect(() => {
if (!onLoadingChange) return;
const loading = !!(
cameraEnabled &&
!offline &&
(!showStillWithoutActivity || isReEnabling) &&
!liveReady
);
onLoadingChange(loading);
}, [
onLoadingChange,
cameraEnabled,
offline,
showStillWithoutActivity,
isReEnabling,
liveReady,
]);
onLoadingChangeRef.current?.(loading);
}, [cameraEnabled, offline, showStillWithoutActivity, isReEnabling, liveReady]);
useEffect(() => {
if (!onActiveMotionChange) return;
const motionVisible = !!(
autoLive &&
!offline &&
activeMotion &&
((showStillWithoutActivity && !liveReady) || liveReady)
);
onActiveMotionChange(motionVisible);
}, [
onActiveMotionChange,
autoLive,
offline,
activeMotion,
showStillWithoutActivity,
liveReady,
]);
onActiveMotionChangeRef.current?.(motionVisible);
}, [autoLive, offline, activeMotion, showStillWithoutActivity, liveReady]);
if (!cameraConfig) {
return <ActivityIndicator />;
@ -453,7 +444,7 @@ export default function LivePlayer({
/>
</div>
{!onLoadingChange &&
{!onLoadingChangeRef.current &&
cameraEnabled &&
!offline &&
(!showStillWithoutActivity || isReEnabling) &&
@ -577,7 +568,7 @@ export default function LivePlayer({
{cameraName}
</Chip>
)}
{!onActiveMotionChange &&
{!onActiveMotionChangeRef.current &&
autoLive &&
!offline &&
activeMotion &&
@ -585,7 +576,7 @@ export default function LivePlayer({
<MdCircle className="mr-2 size-2 animate-pulse text-danger shadow-danger drop-shadow-md" />
)}
</div>
{showStats && !onStatsUpdate && (
{showStats && !onStatsUpdateRef.current && (
<PlayerStats stats={stats} minimal={cameraRef !== undefined} />
)}
</div>