From e1055e0c79d97c8326520688d179a404564412c0 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Mon, 20 Jan 2025 12:58:59 -0600 Subject: [PATCH] Prevent multiple link spinners showing --- src/components/LinkWithStatus.tsx | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/components/LinkWithStatus.tsx b/src/components/LinkWithStatus.tsx index 8b0876a3..5655454d 100644 --- a/src/components/LinkWithStatus.tsx +++ b/src/components/LinkWithStatus.tsx @@ -14,7 +14,7 @@ import clsx from 'clsx/lite'; // Avoid showing spinner for too short a time const FLICKER_THRESHOLD = 400; -// Clear loading status after long duration of inactivity +// Clear loading status after long duration const MAX_LOADING_DURATION = 15_000; export type LinkWithStatusProps = ComponentProps & { @@ -35,6 +35,7 @@ export default function LinkWithStatus({ }: LinkWithStatusProps) { const path = usePathname(); + const [pathWhenClicked, setPathWhenClicked] = useState(); const [isLoading, setIsLoading] = useState(false); const isLoadingStartTime = useRef(undefined); @@ -43,31 +44,40 @@ export default function LinkWithStatus({ const stopLoadingTimeout = useRef(undefined); const maxLoadingTimeout = useRef(undefined); - const clearTimeouts = useCallback(() => + const clearTimeouts = useCallback(() => { [startLoadingTimeout, stopLoadingTimeout, maxLoadingTimeout] .forEach(timeout => { if (timeout.current) { clearTimeout(timeout.current); } - }), - []); + }); + }, []); + + const stopLoading = useCallback(() => { + setIsLoading(false); + setPathWhenClicked(undefined); + }, []); const isVisitingLinkHref = path === href; + const shouldCancelLoading = + (pathWhenClicked && pathWhenClicked !== path) || + isVisitingLinkHref; + useEffect(() => { - if (isVisitingLinkHref) { + if (shouldCancelLoading) { clearTimeouts(); const loadingDuration = isLoadingStartTime.current ? Date.now() - isLoadingStartTime.current : 0; if (loadingDuration < FLICKER_THRESHOLD) { stopLoadingTimeout.current = setTimeout( - () => { setIsLoading(false); }, + stopLoading, FLICKER_THRESHOLD - loadingDuration, ); } else { - setIsLoading(false); + stopLoading(); } } - }, [isVisitingLinkHref, clearTimeouts]); + }, [shouldCancelLoading, clearTimeouts, stopLoading]); // Clear timeouts when unmounting useEffect(() => () => clearTimeouts(), [clearTimeouts]); @@ -83,6 +93,7 @@ export default function LinkWithStatus({ onClick={e => { const isOpeningNewTab = e.metaKey || e.ctrlKey; if (!isVisitingLinkHref && !isOpeningNewTab) { + setPathWhenClicked(path); startLoadingTimeout.current = setTimeout( () => { isLoadingStartTime.current = Date.now(); @@ -91,7 +102,7 @@ export default function LinkWithStatus({ FLICKER_THRESHOLD, ); maxLoadingTimeout.current = setTimeout( - () => { setIsLoading(false); }, + stopLoading, MAX_LOADING_DURATION, ); }