From 459a92335a7afd6c3300d77ec1c0eeac46460c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20HERV=C3=89?= Date: Sat, 18 Apr 2026 17:39:13 +0200 Subject: [PATCH] Fixed: Scroll position not reset when navigating on small screens --- frontend/src/Collection/Collection.js | 7 +--- .../src/Collection/CollectionConnector.js | 12 +------ .../src/Components/Page/PageContentBody.tsx | 23 +++++++----- .../src/Components/withScrollPosition.tsx | 31 ---------------- frontend/src/DiscoverMovie/DiscoverMovie.js | 7 +--- .../DiscoverMovie/DiscoverMovieConnector.js | 12 +------ .../src/Helpers/Hooks/useScrollPosition.ts | 35 +++++++++++++++++++ frontend/src/Movie/Index/MovieIndex.tsx | 22 ++++-------- 8 files changed, 60 insertions(+), 89 deletions(-) delete mode 100644 frontend/src/Components/withScrollPosition.tsx create mode 100644 frontend/src/Helpers/Hooks/useScrollPosition.ts diff --git a/frontend/src/Collection/Collection.js b/frontend/src/Collection/Collection.js index 7982cafd7f..f5c296c853 100644 --- a/frontend/src/Collection/Collection.js +++ b/frontend/src/Collection/Collection.js @@ -224,8 +224,6 @@ class Collection extends Component { view, onSortSelect, onFilterSelect, - initialScrollTop, - onScroll, isRefreshingCollections, isSaving, isAdding, @@ -307,7 +305,7 @@ class Collection extends Component { ref={this.scrollerRef} className={styles.contentBody} innerClassName={styles[`${view}InnerContentBody`]} - onScroll={onScroll} + scrollPositionKey="movieCollections" > { isFetching && !isPopulated && @@ -336,7 +334,6 @@ class Collection extends Component { onSelectedChange={this.onSelectedChange} onSelectAllChange={this.onSelectAllChange} selectedState={selectedState} - scrollTop={initialScrollTop} {...otherProps} /> @@ -377,7 +374,6 @@ class Collection extends Component { } Collection.propTypes = { - initialScrollTop: PropTypes.number, isFetching: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired, @@ -395,7 +391,6 @@ Collection.propTypes = { isSmallScreen: PropTypes.bool.isRequired, onSortSelect: PropTypes.func.isRequired, onFilterSelect: PropTypes.func.isRequired, - onScroll: PropTypes.func.isRequired, onUpdateSelectedPress: PropTypes.func.isRequired, onRefreshMovieCollectionsPress: PropTypes.func.isRequired }; diff --git a/frontend/src/Collection/CollectionConnector.js b/frontend/src/Collection/CollectionConnector.js index 7a1caeb878..75308eafbc 100644 --- a/frontend/src/Collection/CollectionConnector.js +++ b/frontend/src/Collection/CollectionConnector.js @@ -3,7 +3,6 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import * as commandNames from 'Commands/commandNames'; -import withScrollPosition from 'Components/withScrollPosition'; import { executeCommand } from 'Store/Actions/commandActions'; import { fetchMovieCollections, @@ -12,7 +11,6 @@ import { setMovieCollectionsSort } from 'Store/Actions/movieCollectionActions'; import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions'; -import scrollPositions from 'Store/scrollPositions'; import createCollectionClientSideCollectionItemsSelector from 'Store/Selectors/createCollectionClientSideCollectionItemsSelector'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; @@ -82,10 +80,6 @@ class CollectionConnector extends Component { // // Listeners - onScroll = ({ scrollTop }) => { - scrollPositions.movieCollections = scrollTop; - }; - onUpdateSelectedPress = (payload) => { this.props.onUpdateSelectedPress(payload); }; @@ -105,7 +99,6 @@ class CollectionConnector extends Component { ); @@ -121,7 +114,4 @@ CollectionConnector.propTypes = { dispatchClearQueueDetails: PropTypes.func.isRequired }; -export default withScrollPosition( - connect(createMapStateToProps, createMapDispatchToProps)(CollectionConnector), - 'movieCollections' -); +export default connect(createMapStateToProps, createMapDispatchToProps)(CollectionConnector); diff --git a/frontend/src/Components/Page/PageContentBody.tsx b/frontend/src/Components/Page/PageContentBody.tsx index 9c3ffcd0a3..89d7566455 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 f688a6253d..0000000000 --- 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/DiscoverMovie/DiscoverMovie.js b/frontend/src/DiscoverMovie/DiscoverMovie.js index cd8bfc230a..c598d552c9 100644 --- a/frontend/src/DiscoverMovie/DiscoverMovie.js +++ b/frontend/src/DiscoverMovie/DiscoverMovie.js @@ -259,8 +259,6 @@ class DiscoverMovie extends Component { onSortSelect, onFilterSelect, onViewSelect, - initialScrollTop, - onScroll, onAddMoviesPress, isSyncingLists, ...otherProps @@ -370,7 +368,7 @@ class DiscoverMovie extends Component { ref={this.scrollerRef} className={styles.contentBody} innerClassName={styles[`${view}InnerContentBody`]} - onScroll={onScroll} + scrollPositionKey="discoverMovie" > { isFetching && !isPopulated && @@ -399,7 +397,6 @@ class DiscoverMovie extends Component { onSelectedChange={this.onSelectedChange} onSelectAllChange={this.onSelectAllChange} selectedState={selectedState} - scrollTop={initialScrollTop} {...otherProps} /> @@ -444,7 +441,6 @@ class DiscoverMovie extends Component { } DiscoverMovie.propTypes = { - initialScrollTop: PropTypes.number, isFetching: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired, error: PropTypes.object, @@ -465,7 +461,6 @@ DiscoverMovie.propTypes = { onSortSelect: PropTypes.func.isRequired, onFilterSelect: PropTypes.func.isRequired, onViewSelect: PropTypes.func.isRequired, - onScroll: PropTypes.func.isRequired, onAddMoviesPress: PropTypes.func.isRequired, onExcludeMoviesPress: PropTypes.func.isRequired, onImportListSyncPress: PropTypes.func.isRequired, diff --git a/frontend/src/DiscoverMovie/DiscoverMovieConnector.js b/frontend/src/DiscoverMovie/DiscoverMovieConnector.js index 9441b74b82..f0c1ae6296 100644 --- a/frontend/src/DiscoverMovie/DiscoverMovieConnector.js +++ b/frontend/src/DiscoverMovie/DiscoverMovieConnector.js @@ -3,11 +3,9 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import * as commandNames from 'Commands/commandNames'; -import withScrollPosition from 'Components/withScrollPosition'; import { executeCommand } from 'Store/Actions/commandActions'; import { addImportListExclusions, addMovies, clearAddMovie, fetchDiscoverMovies, setListMovieFilter, setListMovieSort, setListMovieTableOption, setListMovieView } from 'Store/Actions/discoverMovieActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; -import scrollPositions from 'Store/scrollPositions'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import createDiscoverMovieClientSideCollectionItemsSelector from 'Store/Selectors/createDiscoverMovieClientSideCollectionItemsSelector'; @@ -106,10 +104,6 @@ class DiscoverMovieConnector extends Component { this.props.dispatchSetListMovieView(view); }; - onScroll = ({ scrollTop }) => { - scrollPositions.discoverMovie = scrollTop; - }; - onAddMoviesPress = ({ ids, addOptions }) => { this.props.dispatchAddMovies(ids, addOptions); }; @@ -126,7 +120,6 @@ class DiscoverMovieConnector extends Component { { + 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/Movie/Index/MovieIndex.tsx b/frontend/src/Movie/Index/MovieIndex.tsx index 8ac8db9a32..b87304940e 100644 --- a/frontend/src/Movie/Index/MovieIndex.tsx +++ b/frontend/src/Movie/Index/MovieIndex.tsx @@ -21,7 +21,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 { align, icons, kinds } from 'Helpers/Props'; import { DESCENDING } from 'Helpers/Props/sortDirections'; import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal'; @@ -35,7 +34,6 @@ import { setMovieView, } from 'Store/Actions/movieIndexActions'; import { fetchQueueDetails } from 'Store/Actions/queueActions'; -import scrollPositions from 'Store/scrollPositions'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import createMovieClientSideCollectionItemsSelector from 'Store/Selectors/createMovieClientSideCollectionItemsSelector'; @@ -72,11 +70,7 @@ function getViewComponent(view: string) { return MovieIndexTable; } -interface MovieIndexProps { - initialScrollTop?: number; -} - -const MovieIndex = withScrollPosition((props: MovieIndexProps) => { +function MovieIndex() { const history = useHistory(); const { @@ -186,13 +180,9 @@ const MovieIndex = withScrollPosition((props: MovieIndexProps) => { [setJumpToCharacter] ); - const onScroll = useCallback( - ({ scrollTop }: { scrollTop: number }) => { - setJumpToCharacter(undefined); - scrollPositions.movieIndex = scrollTop; - }, - [setJumpToCharacter] - ); + const onScroll = useCallback(() => { + setJumpToCharacter(undefined); + }, [setJumpToCharacter]); const jumpBarItems: PageJumpBarItems = useMemo(() => { // Reset if not sorting by sortTitle @@ -345,7 +335,7 @@ const MovieIndex = withScrollPosition((props: MovieIndexProps) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore innerClassName={styles[`${view}InnerContentBody`]} - initialScrollTop={props.initialScrollTop} + scrollPositionKey="movieIndex" onScroll={onScroll} > {isFetching && !isPopulated ? : null} @@ -407,6 +397,6 @@ const MovieIndex = withScrollPosition((props: MovieIndexProps) => { ); -}, 'movieIndex'); +} export default MovieIndex;