From d0c871d22bb5fbbea8222174c58a1dadfac691a2 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 4 Jun 2025 01:12:54 +0300 Subject: [PATCH] fix scrollto --- src/common/VirtualizedList.tsx | 2 +- src/common/useScrollTracker.tsx | 7 ++- src/common/useScrollTrackerRef.tsx | 57 ++++++++++++------- .../ResourceList/ResourceListDisplay.tsx | 14 ++--- src/hooks/useResources.tsx | 2 +- src/state/cache.ts | 4 +- 6 files changed, 53 insertions(+), 33 deletions(-) diff --git a/src/common/VirtualizedList.tsx b/src/common/VirtualizedList.tsx index c038a3c..815b875 100644 --- a/src/common/VirtualizedList.tsx +++ b/src/common/VirtualizedList.tsx @@ -19,7 +19,7 @@ interface PropsVirtualizedList { } export const VirtualizedList = ({ list, children, onSeenLastItem, listName }: PropsVirtualizedList) => { const parentRef = useRef(null); -useScrollTrackerRef(listName, parentRef) + useScrollTrackerRef(listName, list?.length > 0, parentRef) const rowVirtualizer = useVirtualizer({ count: list.length, diff --git a/src/common/useScrollTracker.tsx b/src/common/useScrollTracker.tsx index 04ae71a..2f0980f 100644 --- a/src/common/useScrollTracker.tsx +++ b/src/common/useScrollTracker.tsx @@ -26,10 +26,11 @@ export const useScrollTracker = (listName: string, hasList: boolean, disableScro }; window.addEventListener("scroll", handleScroll); - return () => { - sessionStorage.setItem(SCROLL_KEY, scrollPositionRef.current.toString()); - window.removeEventListener("scroll", handleScroll); + if(!disableScrollTracker){ + sessionStorage.setItem(SCROLL_KEY, scrollPositionRef.current.toString()); + window.removeEventListener("scroll", handleScroll); + } }; }, [listName, hasList, disableScrollTracker]); diff --git a/src/common/useScrollTrackerRef.tsx b/src/common/useScrollTrackerRef.tsx index 118c13a..914f051 100644 --- a/src/common/useScrollTrackerRef.tsx +++ b/src/common/useScrollTrackerRef.tsx @@ -1,30 +1,49 @@ -import { useEffect } from "react"; +import { useEffect, useRef, useState } from "react"; -export const useScrollTrackerRef = (listName: string, ref: React.RefObject) => { +export const useScrollTrackerRef = (listName: string, hasList: boolean, scrollerRef: React.RefObject | undefined) => { + const [hasMounted, setHasMounted] = useState(false) + const hasScrollRef = useRef(false) useEffect(() => { - if (!listName || !ref.current) return; + if (!listName || !scrollerRef?.current || !hasMounted) return; const SCROLL_KEY = `scroll-position-${listName}`; - const savedPosition = sessionStorage.getItem(SCROLL_KEY); - - - if (savedPosition && ref.current) { - ref.current.scrollTop = parseInt(savedPosition, 10); - } - - const handleScroll = () => { - if (ref.current) { - sessionStorage.setItem(SCROLL_KEY, ref.current.scrollTop.toString()); - } + if(!scrollerRef.current) return + sessionStorage.setItem(SCROLL_KEY, scrollerRef.current.scrollTop.toString()); }; - ref.current.addEventListener("scroll", handleScroll); + scrollerRef.current.addEventListener("scroll", handleScroll); return () => { - if (ref.current) { - ref.current.removeEventListener("scroll", handleScroll); + if (scrollerRef.current) { + scrollerRef.current.removeEventListener("scroll", handleScroll); } }; - }, [listName, ref]); -}; \ No newline at end of file + }, [listName, hasMounted]); + + useEffect(() => { + if (!listName || !hasList || hasScrollRef.current || !scrollerRef?.current) return; + const SCROLL_KEY = `scroll-position-${listName}`; + const savedPosition = sessionStorage.getItem(SCROLL_KEY); + + const attemptScrollRestore = () => { + const el = scrollerRef.current; + const saved = parseInt(savedPosition || '0', 10); + if (!el) return; + if (el.scrollHeight > el.clientHeight && saved <= el.scrollHeight - el.clientHeight) { + + setTimeout(() => { + el.scrollTop = saved; + }, 200); + setHasMounted(true) + hasScrollRef.current = true; + } else { + requestAnimationFrame(attemptScrollRestore); + } + + + }; + + requestAnimationFrame(attemptScrollRestore); + }, [listName, hasList]); +}; diff --git a/src/components/ResourceList/ResourceListDisplay.tsx b/src/components/ResourceList/ResourceListDisplay.tsx index e35cd3d..f3cd9e2 100644 --- a/src/components/ResourceList/ResourceListDisplay.tsx +++ b/src/components/ResourceList/ResourceListDisplay.tsx @@ -24,6 +24,7 @@ import { HorizontalPaginatedList } from "./HorizontalPaginationList"; import { VerticalPaginatedList } from "./VerticalPaginationList"; import { useIdentifiers } from "../../hooks/useIdentifiers"; import { useGlobal } from "../../context/GlobalProvider"; +import { useScrollTrackerRef } from "../../common/useScrollTrackerRef"; type Direction = "VERTICAL" | "HORIZONTAL"; interface ResourceListStyles { @@ -82,6 +83,7 @@ interface BaseProps { } onNewData?: (hasNewData: boolean) => void; ref?: any + scrollerRef?: React.RefObject } // ✅ Restrict `direction` only when `disableVirtualization = false` @@ -121,7 +123,8 @@ export const MemorizedComponent = ({ onResults, searchNewData, onNewData, - ref + ref, + scrollerRef }: PropsResourceListDisplay) => { const {identifierOperations, lists} = useGlobal() const memoizedParams = useMemo(() => JSON.stringify(search), [search]); @@ -271,22 +274,19 @@ const addItems = useListStore((s) => s.addItems); const parsedParams = {...(JSON.parse(memoizedParamsRef.current))}; parsedParams.identifier = generatedIdentifier const stringedParams = JSON.stringify(parsedParams) - - if(stringedParams === isListExpiredRef.current && !initialized.current){ + if(stringedParams === isListExpiredRef.current){ setIsLoading(false) - initialized.current = true return } } - sessionStorage.removeItem(`scroll-position-${listName}`); prevGeneratedIdentifierRef.current = generatedIdentifier getResourceList(); }, [getResourceList, generatedIdentifier]); // Runs when dependencies change - const {elementRef} = useScrollTracker(listName, list?.length > 0, disableScrollTracker); - + const {elementRef} = useScrollTracker(listName, list?.length > 0, scrollerRef ? true : !disableVirtualization ? true : disableScrollTracker); + useScrollTrackerRef(listName, list?.length > 0, scrollerRef) const setSearchCacheExpiryDuration = useCacheStore((s) => s.setSearchCacheExpiryDuration); const setResourceCacheExpiryDuration = useCacheStore((s) => s.setResourceCacheExpiryDuration); diff --git a/src/hooks/useResources.tsx b/src/hooks/useResources.tsx index 584cbff..f169922 100644 --- a/src/hooks/useResources.tsx +++ b/src/hooks/useResources.tsx @@ -240,7 +240,7 @@ export const useResources = (retryAttempts: number = 2) => { if (!lastCreated) break; } - setSearchCache(listName, cacheKey, filteredResults, JSON.stringify(params)); + setSearchCache(listName, cacheKey, filteredResults, cancelRequests ? JSON.stringify(params) : null); fetchDataFromResults(filteredResults, returnType); return filteredResults; diff --git a/src/state/cache.ts b/src/state/cache.ts index c9ea99a..555d527 100644 --- a/src/state/cache.ts +++ b/src/state/cache.ts @@ -56,7 +56,7 @@ interface CacheState { // Search cache actions setResourceCache: (id: string, data: ListItem | false | null, customExpiry?: number) => void; - setSearchCache: (listName: string, searchTerm: string, data: QortalMetadata[], searchParamsStringified: string, customExpiry?: number) => void; + setSearchCache: (listName: string, searchTerm: string, data: QortalMetadata[], searchParamsStringified: string | null, customExpiry?: number) => void; getSearchCache: (listName: string, searchTerm: string) => QortalMetadata[] | null; clearExpiredCache: () => void; getResourceCache: (id: string, ignoreExpire?: boolean) => ListItem | false | null; @@ -122,7 +122,7 @@ export const useCacheStore = create }, temporaryNewResources: state.searchCache[listName]?.temporaryNewResources || [], expiry, - searchParamsStringified + searchParamsStringified: searchParamsStringified === null ? state.searchCache[listName]?.searchParamsStringified : searchParamsStringified }, }, };