Open roeycohen opened 3 years ago
Can you share your fork? Would love to get rid of some console warnings.
hi @mmontag, I don't have it in a fork, but here's the code (if you could create a fork for the community it could be nice 😊):
import React, {memo, useEffect, useRef, useState} from "react";
import PropTypes from 'prop-types';
export const VirtualList = memo(function VirtualList({items = [], itemHeight, itemBuffer, container = window, InnerComponent})
{
const listRef = useRef();
const [range, setRange] = useState({firstItemIndex: 0, lastItemIndex: -1});
let refreshState = () =>
{
if (!listRef.current)
return;
const newRange = getVisibleItemBounds(listRef.current, container, items, itemHeight, itemBuffer);
if (!newRange || newRange.firstItemIndex > newRange.lastItemIndex)
return;
if (newRange.firstItemIndex !== range.firstItemIndex || newRange.lastItemIndex !== range.lastItemIndex)
setRange(newRange);
};
if (typeof window !== 'undefined' && 'requestAnimationFrame' in window)
refreshState = throttleWithRAF(refreshState);
useEffect(refreshState);
useEventListener('scroll', refreshState, container);
useEventListener('resize', refreshState, container);
return <InnerComponent
listRef={listRef}
partialList={range.lastItemIndex > -1 ? items.slice(range.firstItemIndex, range.lastItemIndex + 1) : []}
style={{
height: items.length * itemHeight,
paddingTop: range.firstItemIndex * itemHeight,
boxSizing: 'border-box'
}}/>;
}, (prevProps, nextProps) =>
{
return prevProps.items === nextProps.items &&
prevProps.itemBuffer === nextProps.itemBuffer &&
prevProps.itemHeight === nextProps.itemHeight &&
prevProps.container === nextProps.container;
});
VirtualList.propTypes = {
InnerComponent: PropTypes.func,
container: PropTypes.any,
itemBuffer: PropTypes.number,
itemHeight: PropTypes.number,
items: PropTypes.array,
};
const getVisibleItemBounds = (list, container, items, itemHeight, itemBuffer) =>
{
// early return if we can't calculate
if (!container || !itemHeight || !items || items.length === 0)
return undefined;
// what the user can see
const {innerHeight, clientHeight} = container;
const viewHeight = innerHeight || clientHeight; // how many pixels are visible
if (!viewHeight)
return undefined;
const viewTop = getElementTop(container); // top y-coordinate of viewport inside container
const viewBottom = viewTop + viewHeight;
const listTop = topFromWindow(list) - topFromWindow(container); // top y-coordinate of container inside window
const listHeight = itemHeight * items.length;
// visible list inside view
const listViewTop = Math.max(0, viewTop - listTop); // top y-coordinate of list that is visible inside view
const listViewBottom = Math.max(0, Math.min(listHeight, viewBottom - listTop)); // bottom y-coordinate of list that is visible inside view
// visible item indexes
const firstItemIndex = Math.max(0, Math.floor(listViewTop / itemHeight) - itemBuffer);
const lastItemIndex = Math.min(items.length, Math.ceil(listViewBottom / itemHeight) + itemBuffer) - 1;
return {firstItemIndex, lastItemIndex};
};
const topFromWindow = (element) =>
{
if (typeof element === 'undefined' || !element)
return 0;
return (element.offsetTop || 0) + topFromWindow(element.offsetParent);
};
const getElementTop = (element) =>
{
if (element.pageYOffset)
return element.pageYOffset;
if (element.document)
{
if (element.document.documentElement && element.document.documentElement.scrollTop)
return element.document.documentElement.scrollTop;
if (element.document.body && element.document.body.scrollTop)
return element.document.body.scrollTop;
return 0;
}
return element.scrollY || element.scrollTop || 0;
};
const throttleWithRAF = function (fn)
{
let running = false;
return () =>
{
if (running)
return;
running = true;
window.requestAnimationFrame(() =>
{
fn.apply(this, arguments);
running = false;
});
};
};
// based on: https://usehooks.com/useEventListener/
const useEventListener = (eventName, handler, element = window) =>
{
const savedHandler = useRef();
useEffect(() => void (savedHandler.current = handler), [handler]);
useEffect(
() =>
{
const eventListener = event => savedHandler.current(event);
element.addEventListener(eventName, eventListener);
return () => element.removeEventListener(eventName, eventListener);
},
[eventName, element] // Re-run if eventName or element changes
);
};
also, here's a sample code on how to use it (I've extracted it from my project, hope it will help you although i'm using it with tables and not lists):
const innerTableComponent = useCallback(({partialList, style, listRef}) =>
<div css={styleTableList(compact)}>
<table style={style} ref={listRef}>
{thead}
<tbody>
{partialList.map(r => <ErrorBoundary key={r[rowIdCol]}><RowRenderer row={r} rowProps={rowProps}/></ErrorBoundary>)}
<tr style={{height: 'auto'}}/>
</tbody>
</table>
</div>
);
return <VirtualList
items={_collection}
itemHeight={50}
itemBuffer={15}
container={scrollParent || window}
InnerComponent={innerTableComponent}
/>;
hi @mmontag, let me know if this code helped you... or if you have any comments.
Does it work with tbody?
hi @wuarmin, we're using it with Tables if that's what you're asking...
Thanks @roeycohen ! Yeah that's my question. I want to use react table and need a working virtualization for tbody, but I need to use html table markup too. WDYT? Best regards
hi @wuarmin, sorry for my late reply... please take a look on my comment from Sep 17, 2021 above... it's a sample with a table :)
Hey @roeycohen!
Thanks. I tested your code, and I'm close to making it work. One question:
I have a container with 400px
height. In my Testcase I have 100 rows with height 26px
, so table has a height of 2600px
. If I scroll down, some upper rows (still in dom) are rendered above the viewport and so they are not visible:
The padding-top
of table has no effect on collapsed tables. I have to set border
to separate
, but that is no option for me. Do you have an idea, how to solve this?
Thanks!
@roeycohen it didn't work for me since it looks like you changed the interface a bit. However, #78 works great without any codechange and would be nice to merge @developerdizzle.
Hi @developerdizzle,
I've reorganized this library code using hooks and react 17. I'm willing to share if you want to update this library...
Roey