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;