ScrollableList: useMemo, useCallback, throttle, refactor, make it nice

environments/review-scroll-pos-dnhc2t/deployments/173
Alex Gleason 2 years ago
parent aecf539581
commit 509b7b871b
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7

@ -1,6 +1,7 @@
import React, { useEffect, useRef } from 'react'; import { throttle } from 'lodash';
import React, { useEffect, useRef, useMemo, useCallback } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { Virtuoso, Components, VirtuosoProps, VirtuosoHandle, ListRange } from 'react-virtuoso'; import { Virtuoso, Components, VirtuosoProps, VirtuosoHandle, ListRange, IndexLocationWithAlign } from 'react-virtuoso';
import PullToRefresh from 'soapbox/components/pull-to-refresh'; import PullToRefresh from 'soapbox/components/pull-to-refresh';
import { useSettings } from 'soapbox/hooks'; import { useSettings } from 'soapbox/hooks';
@ -13,6 +14,7 @@ type Context = {
listClassName?: string, listClassName?: string,
} }
/** Scroll position saved in sessionStorage. */
type SavedScrollPosition = { type SavedScrollPosition = {
index: number, index: number,
offset: number, offset: number,
@ -55,6 +57,7 @@ interface IScrollableList extends VirtuosoProps<any, any> {
/** Legacy ScrollableList with Virtuoso for backwards-compatibility */ /** Legacy ScrollableList with Virtuoso for backwards-compatibility */
const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
scrollKey,
prepend = null, prepend = null,
alwaysPrepend, alwaysPrepend,
children, children,
@ -80,8 +83,8 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
const autoloadMore = settings.get('autoloadMore'); const autoloadMore = settings.get('autoloadMore');
// Preserve scroll position // Preserve scroll position
const scrollDataKey = `soapbox:scrollData:${location.pathname}`; const scrollDataKey = `soapbox:scrollData:${scrollKey}`;
const scrollData: SavedScrollPosition | null = JSON.parse(sessionStorage.getItem(scrollDataKey)!); const scrollData: SavedScrollPosition | null = useMemo(() => JSON.parse(sessionStorage.getItem(scrollDataKey)!), []);
const topIndex = useRef<number>(scrollData ? scrollData.index : 0); const topIndex = useRef<number>(scrollData ? scrollData.index : 0);
const topOffset = useRef<number>(scrollData ? scrollData.offset : 0); const topOffset = useRef<number>(scrollData ? scrollData.offset : 0);
@ -103,20 +106,23 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
data.push(<Spinner />); data.push(<Spinner />);
} }
const handleScroll = () => { const handleScroll = useCallback(throttle(() => {
// HACK: Virtuoso has no better way to get this...
const node = document.querySelector(`[data-virtuoso-scroller] [data-item-index="${topIndex.current}"]`); const node = document.querySelector(`[data-virtuoso-scroller] [data-item-index="${topIndex.current}"]`);
if (node) { if (node) {
topOffset.current = node.getBoundingClientRect().top * -1; topOffset.current = node.getBoundingClientRect().top * -1;
} }
}; }, 150, { trailing: true }), []);
useEffect(() => { useEffect(() => {
document.addEventListener('scroll', handleScroll); document.addEventListener('scroll', handleScroll);
sessionStorage.removeItem(scrollDataKey); sessionStorage.removeItem(scrollDataKey);
return () => { return () => {
const data = { index: topIndex.current, offset: topOffset.current }; if (scrollKey) {
sessionStorage.setItem(scrollDataKey, JSON.stringify(data)); const data: SavedScrollPosition = { index: topIndex.current, offset: topOffset.current };
sessionStorage.setItem(scrollDataKey, JSON.stringify(data));
}
document.removeEventListener('scroll', handleScroll); document.removeEventListener('scroll', handleScroll);
}; };
}, []); }, []);
@ -166,6 +172,22 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
handleScroll(); handleScroll();
}; };
/** Figure out the initial index to scroll to. */
const initialIndex = useMemo<number | IndexLocationWithAlign>(() => {
if (showLoading) return 0;
if (initialTopMostItemIndex) return initialTopMostItemIndex;
if (scrollData && history.action === 'POP') {
return {
align: 'start',
index: scrollData.index,
offset: scrollData.offset,
};
}
return 0;
}, [showLoading, initialTopMostItemIndex]);
/** Render the actual Virtuoso list */ /** Render the actual Virtuoso list */
const renderFeed = (): JSX.Element => ( const renderFeed = (): JSX.Element => (
<Virtuoso <Virtuoso
@ -178,7 +200,7 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
endReached={handleEndReached} endReached={handleEndReached}
isScrolling={isScrolling => isScrolling && onScroll && onScroll()} isScrolling={isScrolling => isScrolling && onScroll && onScroll()}
itemContent={renderItem} itemContent={renderItem}
initialTopMostItemIndex={showLoading ? 0 : initialTopMostItemIndex || (scrollData && history.action === 'POP' ? { align: 'start', index: scrollData.index, offset: scrollData.offset } : 0)} initialTopMostItemIndex={initialIndex}
rangeChanged={handleRangeChange} rangeChanged={handleRangeChange}
style={style} style={style}
context={{ context={{

Loading…
Cancel
Save