sforkowany z mirror/soapbox
ScrollableList: useMemo, useCallback, throttle, refactor, make it nice
rodzic
aecf539581
commit
509b7b871b
|
@ -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) {
|
||||||
|
const data: SavedScrollPosition = { index: topIndex.current, offset: topOffset.current };
|
||||||
sessionStorage.setItem(scrollDataKey, JSON.stringify(data));
|
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={{
|
||||||
|
|
Ładowanie…
Reference in New Issue