mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-12 11:27:34 +03:00
fix React 19 infinite re-render loop on live dashboard
The "Maximum update depth exceeded" error was caused by two issues:
1. useDeferredStreamMetadata returned a new `{}` default on every render
when SWR data was undefined, creating an unstable reference that
triggered the useEffect in useCameraLiveMode on every render cycle.
Fixed by using a stable module-level EMPTY_METADATA constant.
2. useResizeObserver's rest parameter `...refs` created a new array on
every render, causing its useEffect to re-run and re-observe elements
continuously. Fixed by stabilizing refs with useRef and only
reconnecting the observer when actual DOM elements change.
This commit is contained in:
parent
4e37035d87
commit
93889b221c
@ -1,4 +1,4 @@
|
||||
import { MutableRefObject, useEffect, useMemo, useState } from "react";
|
||||
import { MutableRefObject, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
type RefType = MutableRefObject<Element | null> | Window;
|
||||
|
||||
@ -31,25 +31,39 @@ export function useResizeObserver(...refs: RefType[]) {
|
||||
[],
|
||||
);
|
||||
|
||||
// Stabilize refs array to prevent useEffect from re-running on every render
|
||||
// (rest params create a new array each call)
|
||||
const stableRefs = useRef(refs);
|
||||
stableRefs.current = refs;
|
||||
|
||||
// Track observed elements to detect actual changes
|
||||
const observedElements = useRef<(Element | null)[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
refs.forEach((ref) => {
|
||||
if (ref instanceof Window) {
|
||||
resizeObserver.observe(document.body);
|
||||
} else if (ref.current) {
|
||||
resizeObserver.observe(ref.current);
|
||||
}
|
||||
const currentRefs = stableRefs.current;
|
||||
const elements = currentRefs.map((ref) =>
|
||||
ref instanceof Window ? document.body : ref.current,
|
||||
);
|
||||
|
||||
// Only re-observe if actual DOM elements changed
|
||||
const prevElements = observedElements.current;
|
||||
const changed =
|
||||
elements.length !== prevElements.length ||
|
||||
elements.some((el, i) => el !== prevElements[i]);
|
||||
|
||||
if (!changed) return;
|
||||
|
||||
// Disconnect all and re-observe
|
||||
resizeObserver.disconnect();
|
||||
elements.forEach((el) => {
|
||||
if (el) resizeObserver.observe(el);
|
||||
});
|
||||
observedElements.current = elements;
|
||||
|
||||
return () => {
|
||||
refs.forEach((ref) => {
|
||||
if (ref instanceof Window) {
|
||||
resizeObserver.unobserve(document.body);
|
||||
} else if (ref.current) {
|
||||
resizeObserver.unobserve(ref.current);
|
||||
}
|
||||
});
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [refs, resizeObserver]);
|
||||
});
|
||||
|
||||
if (dimensions.length == refs.length) {
|
||||
return dimensions;
|
||||
|
||||
@ -5,6 +5,7 @@ import { LiveStreamMetadata } from "@/types/live";
|
||||
|
||||
const FETCH_TIMEOUT_MS = 10000;
|
||||
const DEFER_DELAY_MS = 2000;
|
||||
const EMPTY_METADATA: { [key: string]: LiveStreamMetadata } = {};
|
||||
|
||||
/**
|
||||
* Hook that fetches go2rtc stream metadata with deferred loading.
|
||||
@ -77,7 +78,7 @@ export default function useDeferredStreamMetadata(streamNames: string[]) {
|
||||
return metadata;
|
||||
}, []);
|
||||
|
||||
const { data: metadata = {} } = useSWR<{
|
||||
const { data: metadata = EMPTY_METADATA } = useSWR<{
|
||||
[key: string]: LiveStreamMetadata;
|
||||
}>(swrKey, fetcher, {
|
||||
revalidateOnFocus: false,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user