Review

Virtualize cryo-drawer sample rail for freezer tablets

CryoBay techs were lagging when scrolling 8k+ vial slots on the cart-mounted freezer tablets. Drop the full map for @tanstack/react-virtual so only the visible drawer rows mount, keep the 32px compact row chrome, and wire row clicks to the pick queue so a tech can queue a pull without lifting gloves.

ReactTier 3reactvirtualizationlayouttanstack-virtual

Click a line to flag it, pick one or more labels, then submit. If the change looks correct, approve it.

src/features/cryobay/CryoDrawerRail.tsx+42-13
11import { useRef } from "react";
2+import { useVirtualizer } from "@tanstack/react-virtual";
23import type { CryoSlot } from "@/api/cryobay";
34
45type Props = {
56 slots: CryoSlot[];
67 onQueuePull: (slotId: string) => void;
78};
89
910export function CryoDrawerRail({ slots, onQueuePull }: Props) {
11+ const parentRef = useRef<HTMLDivElement>(null);
12+ const rowVirtualizer = useVirtualizer({
13+ count: slots.length,
14+ getScrollElement: () => parentRef.current,
15+ // Compact rail chrome is 32px in Figma — fixed estimate keeps scroll math cheap
16+ estimateSize: () => 32,
17+ overscan: 8,
18+ });
1019
1120 return (
12- <div className="cryo-drawer-rail">
13- {slots.map((slot) => (
14- <button
15- key={slot.id}
16- type="button"
17- className="cryo-slot-row"
18- onClick={() => onQueuePull(slot.id)}
19- >
20- <span className="cryo-slot-addr">{slot.rack}.{slot.box}.{slot.well}</span>
21- <span className="cryo-slot-note">{slot.aliquotNote}</span>
22- </button>
23- ))}
24- </div>
21+ <div ref={parentRef} className="cryo-drawer-rail" style={{ overflow: "auto", height: "100%" }}>
22+ <div
23+ style={{
24+ height: `${rowVirtualizer.getTotalSize()}px`,
25+ width: "100%",
26+ position: "relative",
27+ }}
28+ >
29+ {rowVirtualizer.getVirtualItems().map((virtualRow) => {
30+ const slot = slots[virtualRow.index]!;
31+ return (
32+ <button
33+ key={slot.id}
34+ type="button"
35+ className="cryo-slot-row"
36+ // aliquotNote can wrap to 2–3 lines on the 7" cart tablet
37+ style={{
38+ position: "absolute",
39+ top: 0,
40+ left: 0,
41+ width: "100%",
42+ height: `${virtualRow.size}px`,
43+ transform: `translateY(${virtualRow.start}px)`,
44+ }}
45+ onClick={() => onQueuePull(slot.id)}
46+ >
47+ <span className="cryo-slot-addr">{slot.rack}.{slot.box}.{slot.well}</span>
48+ <span className="cryo-slot-note">{slot.aliquotNote}</span>
49+ </button>
50+ );
51+ })}
52+ </div>
53+ </div>
2554 );
2655}