2021-03-07 21:05:07 +13:00
|
|
|
import React, { memo, useState, useEffect } from 'react';
|
|
|
|
|
import handleViewport from 'react-in-viewport';
|
|
|
|
|
import { chunk } from 'lodash';
|
|
|
|
|
import ErrorBoundary from './ErrorBoundary';
|
|
|
|
|
|
|
|
|
|
const SmartListBatch = handleViewport(
|
|
|
|
|
({
|
|
|
|
|
items,
|
|
|
|
|
inViewport,
|
|
|
|
|
forwardedRef,
|
|
|
|
|
setItemHeight,
|
|
|
|
|
itemHeight,
|
|
|
|
|
itemComponent: ItemComponent,
|
|
|
|
|
itemProps,
|
|
|
|
|
batchIndex,
|
|
|
|
|
chunkSize,
|
2021-03-09 20:31:01 +13:00
|
|
|
className = '',
|
2021-03-12 22:20:05 +13:00
|
|
|
isFirst,
|
|
|
|
|
isLast,
|
2021-03-07 21:05:07 +13:00
|
|
|
}) => {
|
|
|
|
|
// Listen for changes to our height, and pass it up to our Grid. This is then used to build the
|
|
|
|
|
// placeholder elements when out of viewport. We only care about the first item because this
|
|
|
|
|
// represents the same heights for everything else (in almost all circumstances).
|
|
|
|
|
const { current: { clientHeight } = {} } = forwardedRef;
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (isFirst && clientHeight !== itemHeight) {
|
|
|
|
|
setItemHeight(clientHeight);
|
|
|
|
|
}
|
|
|
|
|
}, [clientHeight]);
|
|
|
|
|
|
|
|
|
|
const itemIndex = (bi = 0, ii = 0) => {
|
|
|
|
|
if (bi === 0) return ii;
|
|
|
|
|
return ii + (chunkSize * bi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2021-03-09 20:31:01 +13:00
|
|
|
<div className={`smart-list__batch ${className}`} ref={forwardedRef}>
|
2021-03-12 22:20:05 +13:00
|
|
|
{inViewport || isFirst || isLast ? (
|
|
|
|
|
<div
|
|
|
|
|
className="smart-list__batch__inner"
|
|
|
|
|
style={isFirst || isLast ? {} : { minHeight: itemHeight }}
|
|
|
|
|
>
|
2021-03-07 21:05:07 +13:00
|
|
|
{
|
|
|
|
|
items.map((item, index) => (
|
|
|
|
|
<ItemComponent
|
|
|
|
|
key={`${index}_${item.uri || item.name}`}
|
|
|
|
|
item={item}
|
|
|
|
|
getItemIndex={() => itemIndex(batchIndex, index)}
|
|
|
|
|
{...itemProps}
|
|
|
|
|
/>
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div style={{ height: itemHeight }} />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const SmartList = memo(({
|
|
|
|
|
items,
|
|
|
|
|
itemComponent,
|
|
|
|
|
chunkSize = 20,
|
|
|
|
|
initialHeight = '50vh',
|
|
|
|
|
itemProps,
|
2021-03-08 20:17:08 +13:00
|
|
|
className,
|
2021-03-07 21:05:07 +13:00
|
|
|
}) => {
|
|
|
|
|
if (!items || !items.length) return null;
|
|
|
|
|
if (!itemComponent) return null;
|
|
|
|
|
|
|
|
|
|
const [itemHeight, setItemHeight] = useState(initialHeight);
|
2021-03-12 22:20:05 +13:00
|
|
|
const chunks = chunk(items, chunkSize);
|
2021-03-07 21:05:07 +13:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<ErrorBoundary>
|
|
|
|
|
{
|
2021-03-12 22:20:05 +13:00
|
|
|
chunks.map((chunked, index) => (
|
2021-03-07 21:05:07 +13:00
|
|
|
<SmartListBatch
|
|
|
|
|
key={`smart-list__batch-${index}`} // Yeah yeah, I know; TODO
|
2021-03-08 20:17:08 +13:00
|
|
|
className={className}
|
2021-03-07 21:05:07 +13:00
|
|
|
items={chunked}
|
|
|
|
|
itemHeight={itemHeight}
|
|
|
|
|
itemComponent={itemComponent}
|
|
|
|
|
itemProps={itemProps}
|
|
|
|
|
batchIndex={index}
|
|
|
|
|
chunkSize={chunkSize}
|
|
|
|
|
setItemHeight={setItemHeight}
|
2021-03-12 22:20:05 +13:00
|
|
|
isFirst={index === 0}
|
|
|
|
|
isLast={index === chunks.length - 1}
|
2021-03-07 21:05:07 +13:00
|
|
|
/>
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
</ErrorBoundary>
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
SmartList,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export {
|
|
|
|
|
SmartList,
|
|
|
|
|
};
|