Skip to content

Commit 360a417

Browse files
authored
fix: list mode scroll fixing (#5895)
1 parent e7e37ef commit 360a417

2 files changed

Lines changed: 39 additions & 12 deletions

File tree

packages/shared/src/hooks/useFeed.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
getNextPageParam,
2020
removeCachedPagePost,
2121
RequestKey,
22+
StaleTime,
2223
updateCachedPagePost,
2324
} from '../lib/query';
2425
import type { MarketingCta } from '../components/marketingCta/common';
@@ -226,6 +227,7 @@ export default function useFeed<T>(
226227
return res;
227228
},
228229
refetchOnMount: false,
230+
gcTime: StaleTime.OneHour,
229231
...options,
230232
enabled: !!query && tokenRefreshed,
231233
refetchOnReconnect: false,

packages/shared/src/hooks/useScrollRestoration.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,58 @@ import { useEffect } from 'react';
33
import { useRouter } from 'next/router';
44

55
const scrollPositions: Record<string, number> = {};
6+
const RESTORE_TIMEOUT_MS = 1000;
7+
8+
const getScrollKey = (asPath: string): string => {
9+
if (typeof window === 'undefined') {
10+
return asPath;
11+
}
12+
const historyKey = (window.history.state as { key?: string } | null)?.key;
13+
return historyKey ? `${asPath}:${historyKey}` : asPath;
14+
};
615

716
export const useScrollRestoration = (): void => {
8-
const { pathname } = useRouter();
17+
const { asPath } = useRouter();
918

1019
useEffect(() => {
1120
const handleScroll = () => {
12-
scrollPositions[pathname] = window.scrollY;
21+
scrollPositions[getScrollKey(asPath)] = window.scrollY;
1322
};
1423

15-
window.addEventListener('scroll', handleScroll);
24+
window.addEventListener('scroll', handleScroll, { passive: true });
1625

1726
return () => {
1827
window.removeEventListener('scroll', handleScroll);
1928
};
20-
}, [pathname]);
29+
}, [asPath]);
2130

2231
useEffect(() => {
23-
const scrollPosition = scrollPositions[pathname] || 0;
32+
const target = scrollPositions[getScrollKey(asPath)] ?? 0;
33+
if (target === 0) {
34+
return undefined;
35+
}
36+
37+
// Wait until the page is tall enough before scrolling, so we don't clamp
38+
// to the bottom while feed content is still hydrating.
39+
const deadline = performance.now() + RESTORE_TIMEOUT_MS;
40+
let frame = 0;
41+
42+
const tick = () => {
43+
const maxScroll =
44+
document.documentElement.scrollHeight - window.innerHeight;
45+
46+
if (maxScroll >= target || performance.now() >= deadline) {
47+
window.scrollTo(0, Math.min(target, Math.max(0, maxScroll)));
48+
return;
49+
}
50+
51+
frame = requestAnimationFrame(tick);
52+
};
2453

25-
// Add a small delay to ensure content is loaded before restoring scroll
26-
// This is especially important for feed pages that load content dynamically
27-
const timeoutId = setTimeout(() => {
28-
window.scrollTo(0, scrollPosition);
29-
}, 50);
54+
frame = requestAnimationFrame(tick);
3055

31-
return () => clearTimeout(timeoutId);
32-
}, [pathname]);
56+
return () => cancelAnimationFrame(frame);
57+
}, [asPath]);
3358
};
3459

3560
export const useManualScrollRestoration = (): void => {

0 commit comments

Comments
 (0)