From 710737f2e2319bdfc775f7d4cf89112ff4d3a5ed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20HERV=C3=89?=
<45267274+thibaultherve@users.noreply.github.com>
Date: Sat, 25 Apr 2026 03:47:12 +0200
Subject: [PATCH] Fixed: Scroll position not reset when navigating on small
screens
---
.../src/Components/Page/PageContentBody.tsx | 23 ++++++++----
.../src/Components/withScrollPosition.tsx | 31 ----------------
.../src/Helpers/Hooks/useScrollPosition.ts | 37 +++++++++++++++++++
frontend/src/Series/Index/SeriesIndex.tsx | 22 +++--------
4 files changed, 58 insertions(+), 55 deletions(-)
delete mode 100644 frontend/src/Components/withScrollPosition.tsx
create mode 100644 frontend/src/Helpers/Hooks/useScrollPosition.ts
diff --git a/frontend/src/Components/Page/PageContentBody.tsx b/frontend/src/Components/Page/PageContentBody.tsx
index 9c3ffcd0a..89d756645 100644
--- a/frontend/src/Components/Page/PageContentBody.tsx
+++ b/frontend/src/Components/Page/PageContentBody.tsx
@@ -1,5 +1,6 @@
import React, { ForwardedRef, forwardRef, ReactNode, useCallback } from 'react';
import Scroller, { OnScroll } from 'Components/Scroller/Scroller';
+import useScrollPosition from 'Helpers/Hooks/useScrollPosition';
import { isLocked } from 'Utilities/scrollLock';
import styles from './PageContentBody.css';
@@ -7,7 +8,7 @@ interface PageContentBodyProps {
className?: string;
innerClassName?: string;
children: ReactNode;
- initialScrollTop?: number;
+ scrollPositionKey?: string;
onScroll?: (payload: OnScroll) => void;
}
@@ -17,26 +18,32 @@ const PageContentBody = forwardRef(
className = styles.contentBody,
innerClassName = styles.innerContentBody,
children,
+ scrollPositionKey,
onScroll,
- ...otherProps
} = props;
- const onScrollWrapper = useCallback(
+ const { initialScrollTop, onScroll: onScrollMemo } =
+ useScrollPosition(scrollPositionKey);
+
+ const handleScroll = useCallback(
(payload: OnScroll) => {
- if (onScroll && !isLocked()) {
- onScroll(payload);
+ if (isLocked()) {
+ return;
}
+
+ onScrollMemo(payload);
+ onScroll?.(payload);
},
- [onScroll]
+ [onScroll, onScrollMemo]
);
return (
{children}
diff --git a/frontend/src/Components/withScrollPosition.tsx b/frontend/src/Components/withScrollPosition.tsx
deleted file mode 100644
index f688a6253..000000000
--- a/frontend/src/Components/withScrollPosition.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import { RouteComponentProps } from 'react-router-dom';
-import scrollPositions from 'Store/scrollPositions';
-
-interface WrappedComponentProps {
- initialScrollTop: number;
-}
-
-interface ScrollPositionProps {
- history: RouteComponentProps['history'];
- location: RouteComponentProps['location'];
- match: RouteComponentProps['match'];
-}
-
-function withScrollPosition(
- WrappedComponent: React.FC,
- scrollPositionKey: string
-) {
- function ScrollPosition(props: ScrollPositionProps) {
- const { history } = props;
-
- const initialScrollTop =
- history.action === 'POP' ? scrollPositions[scrollPositionKey] : 0;
-
- return ;
- }
-
- return ScrollPosition;
-}
-
-export default withScrollPosition;
diff --git a/frontend/src/Helpers/Hooks/useScrollPosition.ts b/frontend/src/Helpers/Hooks/useScrollPosition.ts
new file mode 100644
index 000000000..25e5133e0
--- /dev/null
+++ b/frontend/src/Helpers/Hooks/useScrollPosition.ts
@@ -0,0 +1,37 @@
+import { useCallback, useEffect, useMemo } from 'react';
+import { useHistory, useLocation } from 'react-router';
+import { OnScroll } from 'Components/Scroller/Scroller';
+import scrollPositions from 'Store/scrollPositions';
+
+function useScrollPosition(key?: string) {
+ const { pathname } = useLocation();
+ const { action } = useHistory();
+
+ // Reset window scroll on PUSH/REPLACE (mobile's scroll container).
+ // Reset the scroll position unless we're going back, this will allow the scroll
+ // position to reset when moving forward (PUSH/REPLACE) and restore when
+ // moving backwards (POP).
+ useEffect(() => {
+ if (action !== 'POP') {
+ window.scrollTo(0, 0);
+ }
+ }, [pathname, action]);
+
+ const initialScrollTop = useMemo(
+ () => (key && action === 'POP' ? scrollPositions[key] ?? 0 : 0),
+ [key, action]
+ );
+
+ const onScroll = useCallback(
+ ({ scrollTop }: OnScroll) => {
+ if (key) {
+ scrollPositions[key] = scrollTop;
+ }
+ },
+ [key]
+ );
+
+ return { initialScrollTop, onScroll };
+}
+
+export default useScrollPosition;
diff --git a/frontend/src/Series/Index/SeriesIndex.tsx b/frontend/src/Series/Index/SeriesIndex.tsx
index ea4ab3f76..083c438c4 100644
--- a/frontend/src/Series/Index/SeriesIndex.tsx
+++ b/frontend/src/Series/Index/SeriesIndex.tsx
@@ -14,7 +14,6 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
-import withScrollPosition from 'Components/withScrollPosition';
import { useCustomFiltersList } from 'Filters/useCustomFilters';
import { align, icons, kinds } from 'Helpers/Props';
import { DESCENDING } from 'Helpers/Props/sortDirections';
@@ -27,7 +26,6 @@ import {
useSeriesOptions,
} from 'Series/seriesOptionsStore';
import { FILTERS, useSeriesIndex } from 'Series/useSeries';
-import scrollPositions from 'Store/scrollPositions';
import { TableOptionsChangePayload } from 'typings/Table';
import translate from 'Utilities/String/translate';
import SeriesIndexFilterMenu from './Menus/SeriesIndexFilterMenu';
@@ -60,11 +58,7 @@ function getViewComponent(view: string) {
return SeriesIndexTable;
}
-interface SeriesIndexProps {
- initialScrollTop?: number;
-}
-
-const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
+function SeriesIndex() {
const {
isLoading: isFetching,
isFetched,
@@ -148,13 +142,9 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
[setJumpToCharacter]
);
- const onScroll = useCallback(
- ({ scrollTop }: { scrollTop: number }) => {
- setJumpToCharacter(undefined);
- scrollPositions.seriesIndex = scrollTop;
- },
- [setJumpToCharacter]
- );
+ const onScroll = useCallback(() => {
+ setJumpToCharacter(undefined);
+ }, [setJumpToCharacter]);
const jumpBarItems: PageJumpBarItems = useMemo(() => {
// Reset if not sorting by sortTitle
@@ -296,7 +286,7 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
innerClassName={styles[`${view}InnerContentBody`]}
- initialScrollTop={props.initialScrollTop}
+ scrollPositionKey="seriesIndex"
onScroll={onScroll}
>
{isFetching && !isFetched ? : null}
@@ -353,6 +343,6 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
);
-}, 'seriesIndex');
+}
export default SeriesIndex;