Virtualize item list scrolling

This helps the render time by a lot!
This commit is contained in:
Emi Matchu 2021-06-19 12:36:19 -07:00
parent cc4e1f611f
commit be3a162a8a
3 changed files with 113 additions and 11 deletions

View file

@ -49,6 +49,7 @@
"react-router-hash-link": "^2.4.3",
"react-scripts": "^4.0.1",
"react-transition-group": "^4.3.0",
"react-virtualized": "^9.22.3",
"simple-markdown": "^0.7.2",
"tweenjs": "^1.0.2",
"typescript": "^4.1.3",

View file

@ -25,6 +25,11 @@ import {
import { gql, useMutation, useQuery } from "@apollo/client";
import { Link, useParams } from "react-router-dom";
import { HashLink } from "react-router-hash-link";
import {
List as VirtualizedList,
AutoSizer,
WindowScroller,
} from "react-virtualized";
import { Heading1, Heading3, MajorErrorMessage, usePageTitle } from "./util";
import HangerSpinner from "./components/HangerSpinner";
@ -399,17 +404,10 @@ export function ClosetListContents({
return (
<Box>
{itemsToShow.length > 0 ? (
<Wrap spacing="4" justify="center">
{itemsToShow.map((item) => (
<WrapItem key={item.id}>
<ItemCard
item={item}
variant="grid"
tradeMatchingMode={tradeMatchingMode}
/>
</WrapItem>
))}
</Wrap>
<ClosetItemList
items={itemsToShow}
tradeMatchingMode={tradeMatchingMode}
/>
) : (
<Box fontStyle="italic">This list is empty!</Box>
)}
@ -442,6 +440,79 @@ export function ClosetListContents({
);
}
// HACK: Measured by hand from <SquareItemCard />, plus 16px padding.
const ITEM_CARD_WIDTH = 112 + 16;
const ITEM_CARD_HEIGHT = 171 + 16;
function ClosetItemList({ items, tradeMatchingMode }) {
const renderItem = (item) => (
<ItemCard
key={item.id}
item={item}
variant="grid"
tradeMatchingMode={tradeMatchingMode}
/>
);
// For small lists, we don't bother to virtualize, because it slows down
// scrolling! (This helps a lot on the lists index page.)
if (items.length < 30) {
return (
<Wrap spacing="4" justify="center">
{items.map((item) => (
<WrapItem key={item.id}>{renderItem(item)}</WrapItem>
))}
</Wrap>
);
}
return (
<WindowScroller>
{({ height, isScrolling, onChildScroll, scrollTop, registerChild }) => (
<AutoSizer disableHeight>
{({ width }) => {
const numItemsPerRow = Math.floor(width / ITEM_CARD_WIDTH);
const numRows = Math.ceil(items.length / numItemsPerRow);
return (
<div ref={registerChild}>
<VirtualizedList
autoHeight
height={height}
width={width}
rowCount={numRows}
rowHeight={ITEM_CARD_HEIGHT}
rowRenderer={({ index: rowIndex, key, style }) => {
const firstItemIndex = rowIndex * numItemsPerRow;
const itemsForRow = items.slice(
firstItemIndex,
firstItemIndex + numItemsPerRow
);
return (
<HStack
key={key}
style={style}
spacing="4"
justify="center"
>
{itemsForRow.map(renderItem)}
</HStack>
);
}}
isScrolling={isScrolling}
onScroll={onChildScroll}
scrollTop={scrollTop}
/>
</div>
);
}}
</AutoSizer>
)}
</WindowScroller>
);
}
export function buildClosetListPath(closetList) {
let ownsOrWants;
if (closetList.ownsOrWantsItems === "OWNS") {

View file

@ -7884,6 +7884,11 @@ clone-response@^1.0.2:
dependencies:
mimic-response "^1.0.0"
clsx@^1.0.4:
version "1.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@ -9091,6 +9096,14 @@ dom-helpers@^5.0.1:
"@babel/runtime" "^7.8.7"
csstype "^2.6.7"
dom-helpers@^5.1.3:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
dependencies:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
@ -15993,6 +16006,11 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-refresh@^0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
@ -16145,6 +16163,18 @@ react-transition-group@^4.3.0:
loose-envify "^1.4.0"
prop-types "^15.6.2"
react-virtualized@^9.22.3:
version "9.22.3"
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.3.tgz#f430f16beb0a42db420dbd4d340403c0de334421"
integrity sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw==
dependencies:
"@babel/runtime" "^7.7.2"
clsx "^1.0.4"
dom-helpers "^5.1.3"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-lifecycles-compat "^3.0.4"
react@^17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"