From 067fdb50e19f009710f298e9f94e0b3123ce998e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Mar 2026 12:03:54 +0000 Subject: [PATCH] fix: replace useResizeObserver with useLayoutEffect for reliable container width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useResizeObserver reads ref.current during render (before commit), so on first render ref.current is null, no observation starts, and containerWidth stays 0 if no subsequent re-render happens (e.g. page refresh with cached SWR data). useLayoutEffect runs after refs are committed, so ref.current is always the real DOM element. This fixes both the right-column overflow (no window.innerWidth fallback needed — width is always the actual container width) and the black screen on refresh (containerWidth is reliable before the first paint). https://claude.ai/code/session_01H1sqbcFmtwwsdNTJcJHJWd --- web/src/views/live/DraggableGridLayout.tsx | 38 ++++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/web/src/views/live/DraggableGridLayout.tsx b/web/src/views/live/DraggableGridLayout.tsx index c35bacbf3..1f62943e0 100644 --- a/web/src/views/live/DraggableGridLayout.tsx +++ b/web/src/views/live/DraggableGridLayout.tsx @@ -28,7 +28,7 @@ import { VolumeState, } from "@/types/live"; import { Skeleton } from "@/components/ui/skeleton"; -import { useResizeObserver } from "@/hooks/resize-observer"; + import { isEqual } from "lodash"; import useSWR from "swr"; import { isDesktop, isMobile } from "react-device-detect"; @@ -329,23 +329,25 @@ export default function DraggableGridLayout({ const gridContainerRef = useRef(null); - const [{ width: containerWidth, height: containerHeight }] = - useResizeObserver(gridContainerRef); + const [containerWidth, setContainerWidth] = useState(0); + const [containerHeight, setContainerHeight] = useState(0); - // useResizeObserver reads ref.current at render time, so it may miss the - // initial mount when ref.current is null (e.g. on page refresh with cached - // SWR data). Measure the container synchronously in useLayoutEffect as a - // reliable seed value; containerWidth from ResizeObserver takes over once - // it fires. - const [initialWidth, setInitialWidth] = useState(0); + // useLayoutEffect reads ref.current after commit (refs are set before layout + // effects run), so this reliably fires before the first paint regardless of + // whether SWR triggers subsequent re-renders or not. useLayoutEffect(() => { - if (gridContainerRef.current) { - setInitialWidth(gridContainerRef.current.offsetWidth); - } + const el = gridContainerRef.current; + if (!el) return; + setContainerWidth(el.clientWidth); + setContainerHeight(el.clientHeight); + const ro = new ResizeObserver(([entry]) => { + setContainerWidth(entry.contentRect.width); + setContainerHeight(entry.contentRect.height); + }); + ro.observe(el); + return () => ro.disconnect(); }, []); - const effectiveWidth = containerWidth || initialWidth; - const scrollBarWidth = useMemo(() => { if (containerWidth && containerHeight && containerRef.current) { return ( @@ -356,8 +358,8 @@ export default function DraggableGridLayout({ }, [containerRef, containerHeight, containerWidth]); const availableWidth = useMemo( - () => (scrollBarWidth ? effectiveWidth + scrollBarWidth : effectiveWidth), - [effectiveWidth, scrollBarWidth], + () => (scrollBarWidth ? containerWidth + scrollBarWidth : containerWidth), + [containerWidth, scrollBarWidth], ); const hasScrollbar = useMemo(() => { @@ -373,7 +375,7 @@ export default function DraggableGridLayout({ // subtract container margin, 1 camera takes up at least 4 rows // account for additional margin on bottom of each row return ( - ((availableWidth || window.innerWidth) - 2 * marginValue) / + (availableWidth - 2 * marginValue) / 12 / aspectRatio - marginValue + @@ -724,7 +726,7 @@ export default function DraggableGridLayout({ currentGroups={groups} activeGroup={group} /> - {effectiveWidth > 0 && 0 &&