ScrollableList: useMemo, useCallback, throttle, refactor, make it nice
This commit is contained in:
parent
aecf539581
commit
509b7b871b
1 changed files with 31 additions and 9 deletions
|
@ -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 { 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 { useSettings } from 'soapbox/hooks';
|
||||
|
@ -13,6 +14,7 @@ type Context = {
|
|||
listClassName?: string,
|
||||
}
|
||||
|
||||
/** Scroll position saved in sessionStorage. */
|
||||
type SavedScrollPosition = {
|
||||
index: number,
|
||||
offset: number,
|
||||
|
@ -55,6 +57,7 @@ interface IScrollableList extends VirtuosoProps<any, any> {
|
|||
|
||||
/** Legacy ScrollableList with Virtuoso for backwards-compatibility */
|
||||
const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
||||
scrollKey,
|
||||
prepend = null,
|
||||
alwaysPrepend,
|
||||
children,
|
||||
|
@ -80,8 +83,8 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
|||
const autoloadMore = settings.get('autoloadMore');
|
||||
|
||||
// Preserve scroll position
|
||||
const scrollDataKey = `soapbox:scrollData:${location.pathname}`;
|
||||
const scrollData: SavedScrollPosition | null = JSON.parse(sessionStorage.getItem(scrollDataKey)!);
|
||||
const scrollDataKey = `soapbox:scrollData:${scrollKey}`;
|
||||
const scrollData: SavedScrollPosition | null = useMemo(() => JSON.parse(sessionStorage.getItem(scrollDataKey)!), []);
|
||||
const topIndex = useRef<number>(scrollData ? scrollData.index : 0);
|
||||
const topOffset = useRef<number>(scrollData ? scrollData.offset : 0);
|
||||
|
||||
|
@ -103,20 +106,23 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
|||
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}"]`);
|
||||
if (node) {
|
||||
topOffset.current = node.getBoundingClientRect().top * -1;
|
||||
}
|
||||
};
|
||||
}, 150, { trailing: true }), []);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('scroll', handleScroll);
|
||||
sessionStorage.removeItem(scrollDataKey);
|
||||
|
||||
return () => {
|
||||
const data = { index: topIndex.current, offset: topOffset.current };
|
||||
if (scrollKey) {
|
||||
const data: SavedScrollPosition = { index: topIndex.current, offset: topOffset.current };
|
||||
sessionStorage.setItem(scrollDataKey, JSON.stringify(data));
|
||||
}
|
||||
document.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, []);
|
||||
|
@ -166,6 +172,22 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
|||
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 */
|
||||
const renderFeed = (): JSX.Element => (
|
||||
<Virtuoso
|
||||
|
@ -178,7 +200,7 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
|||
endReached={handleEndReached}
|
||||
isScrolling={isScrolling => isScrolling && onScroll && onScroll()}
|
||||
itemContent={renderItem}
|
||||
initialTopMostItemIndex={showLoading ? 0 : initialTopMostItemIndex || (scrollData && history.action === 'POP' ? { align: 'start', index: scrollData.index, offset: scrollData.offset } : 0)}
|
||||
initialTopMostItemIndex={initialIndex}
|
||||
rangeChanged={handleRangeChange}
|
||||
style={style}
|
||||
context={{
|
||||
|
|
Loading…
Reference in a new issue