From a1ffe5c93682f17eb53d29fcddaeb8c22d413bf9 Mon Sep 17 00:00:00 2001 From: Hiep Le <69354317+hieptl@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:19:30 +0700 Subject: [PATCH] fix(frontend): frontend UI keep flashing (#10352) --- frontend/src/components/layout/container.tsx | 29 +++++++++--------- frontend/src/hooks/use-track-element-width.ts | 30 +++++++++++++++++-- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/layout/container.tsx b/frontend/src/components/layout/container.tsx index 36ec7d02ac..7fbe7f41e0 100644 --- a/frontend/src/components/layout/container.tsx +++ b/frontend/src/components/layout/container.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useRef, useState } from "react"; import { NavTab } from "./nav-tab"; import { ScrollLeftButton } from "./scroll-left-button"; import { ScrollRightButton } from "./scroll-right-button"; @@ -25,18 +25,12 @@ export function Container({ children, className, }: ContainerProps) { - const [containerWidth, setContainerWidth] = useState(0); const [canScrollLeft, setCanScrollLeft] = useState(false); const [canScrollRight, setCanScrollRight] = useState(false); + const [showScrollButtons, setShowScrollButtons] = useState(false); const containerRef = useRef(null); const scrollContainerRef = useRef(null); - // Track container width using ResizeObserver - useTrackElementWidth({ - elementRef: containerRef, - callback: setContainerWidth, - }); - // Check scroll position and update button states const updateScrollButtons = () => { if (scrollContainerRef.current) { @@ -47,10 +41,19 @@ export function Container({ } }; - // Update scroll buttons when tabs change or container width changes - useEffect(() => { - updateScrollButtons(); - }, [labels, containerWidth]); + // Track container width using ResizeObserver + useTrackElementWidth({ + elementRef: containerRef, + callback: (width: number) => { + // Only update scroll button visibility when crossing the threshold + const shouldShowScrollButtons = + width < 598 && Boolean(labels) && labels!.length > 0; + if (shouldShowScrollButtons) { + setShowScrollButtons(shouldShowScrollButtons); + } + updateScrollButtons(); + }, + }); // Scroll functions const scrollLeft = () => { @@ -65,8 +68,6 @@ export function Container({ } }; - const showScrollButtons = containerWidth < 598 && labels && labels.length > 0; - return (
; callback: (width: number) => void; + delay?: number; // Optional delay parameter with default } export const useTrackElementWidth = ({ elementRef, callback, + delay = 100, // Default 100ms delay }: UseTrackElementWidthProps) => { + const timeoutRef = useRef(null); + + // Create debounced callback that only fires after delay + const debouncedCallback = useCallback( + (width: number) => { + // Clear existing timeout + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + // Set new timeout + timeoutRef.current = setTimeout(() => { + callback(width); + }, delay); + }, + [callback, delay], + ); + useEffect(() => { const resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { - callback(entry.contentRect.width); + debouncedCallback(entry.contentRect.width); } }); @@ -21,7 +41,11 @@ export const useTrackElementWidth = ({ } return () => { + // Clean up timeout and observer + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } resizeObserver.disconnect(); }; - }, []); + }, [debouncedCallback]); };