Fixed: Scroll position not reset when navigating on small screens

This commit is contained in:
Thibault HERVÉ 2026-04-25 03:47:12 +02:00 committed by GitHub
parent 0826686dd7
commit 710737f2e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 58 additions and 55 deletions

View file

@ -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 (
<Scroller
ref={ref}
{...otherProps}
className={className}
scrollDirection="vertical"
onScroll={onScrollWrapper}
initialScrollTop={initialScrollTop}
onScroll={handleScroll}
>
<div className={innerClassName}>{children}</div>
</Scroller>

View file

@ -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<WrappedComponentProps>,
scrollPositionKey: string
) {
function ScrollPosition(props: ScrollPositionProps) {
const { history } = props;
const initialScrollTop =
history.action === 'POP' ? scrollPositions[scrollPositionKey] : 0;
return <WrappedComponent {...props} initialScrollTop={initialScrollTop} />;
}
return ScrollPosition;
}
export default withScrollPosition;

View file

@ -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;

View file

@ -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 ? <LoadingIndicator /> : null}
@ -353,6 +343,6 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
</SelectProvider>
</QueueDetailsProvider>
);
}, 'seriesIndex');
}
export default SeriesIndex;