fix(web): force scroll-to-top on tab switch (iOS Safari quirk)

Next.js App Router *should* reset scroll on route change, but iOS
Safari (and occasionally Chrome on Android) preserves the document
scroll position when navigating between routes that share a layout —
the user lands on the new tab halfway down the page and has to scroll
manually.

Adds a useEffect([]) in both AccountsTable and UsersTable that calls
window.scrollTo({top:0, left:0, behavior:'instant'}) on mount. Route
transitions remount the table component (different page module), so
the empty deps array fires exactly once per visit.

'instant' behavior avoids a smooth-scroll animation that would compete
with the loading-skeleton swap.
This commit is contained in:
yiekheng 2026-05-03 11:51:27 +08:00
parent 9b222eec56
commit 850fb71ddd
2 changed files with 15 additions and 0 deletions

View File

@ -75,6 +75,14 @@ export default function AccountsTable({
const [createOpen, setCreateOpen] = useState(false);
const [toast, setToast] = useState<ToastMessage | null>(null);
// iOS Safari (and sometimes Chrome on Android) keeps the document
// scroll position across SPA route changes, so tab-switching from
// /users back to / leaves the user halfway down the page. Force
// scroll-to-top on mount; route transitions remount the table.
useEffect(() => {
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
}, []);
// Accumulated rows from initial server-side fetch + every loadMore.
const [rows, setRows] = useState<Acc[]>(initial);
const [hasMore, setHasMore] = useState<boolean>(initialHasMore);

View File

@ -77,6 +77,13 @@ export default function UsersTable({
const [createOpen, setCreateOpen] = useState(false);
const [toast, setToast] = useState<ToastMessage | null>(null);
// Force scroll-to-top on mount — iOS Safari preserves document scroll
// across SPA route changes, so tab-switching leaves the new page
// halfway down. Route transitions remount the table, so [] deps fire.
useEffect(() => {
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
}, []);
const [rows, setRows] = useState<User[]>(initial);
const [hasMore, setHasMore] = useState<boolean>(initialHasMore);
const [total, setTotal] = useState<number>(initialTotal);