Review
Restore cellar bin list scroll after bottle detail
Sommeliers at Harbor & Vine scroll thousands of bin rows while pulling wine for service, then open a bottle provenance sheet and hit back. Wire FixedSizeList so the bin rail remembers its scrollOffset in sessionStorage and remounts at that offset, so they land on the same rack section instead of jumping to the top of the cellar list.
react-windowTier 2react-windowscroll-restorenavigationux
Click a line to flag it, pick one or more labels, then submit. If the change looks correct, approve it.
src/features/cellar/BottleBinList.tsx+17-3
| 1 | 1 | import { useCallback } from "react"; | |
| 2 | 2 | import { FixedSizeList as List, type ListOnScrollProps } from "react-window"; | |
| 3 | 3 | import { useNavigate } from "react-router-dom"; | |
| 4 | 4 | import type { CellarBin } from "@/api/cellar"; | |
| 5 | 5 | ||
| 6 | + | const SCROLL_KEY = "cellar:bin-list-scroll"; | |
| 7 | + | ||
| 6 | 8 | type Props = { bins: CellarBin[] }; | |
| 7 | 9 | ||
| 8 | 10 | export function BottleBinList({ bins }: Props) { | |
| 9 | 11 | const navigate = useNavigate(); | |
| 12 | + | const initialOffset = Number(sessionStorage.getItem(SCROLL_KEY) ?? 0); | |
| 13 | + | ||
| 14 | + | const onScroll = useCallback(({ scrollOffset }: ListOnScrollProps) => { | |
| 15 | + | sessionStorage.setItem(SCROLL_KEY, String(scrollOffset)); | |
| 16 | + | }, []); | |
| 10 | 17 | ||
| 11 | 18 | const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => { | |
| 12 | 19 | const bin = bins[index]!; | |
| 13 | 20 | return ( | |
| 14 | 21 | <button type="button" style={style} onClick={() => navigate(`/cellar/bins/${bin.id}`)}> | |
| 15 | 22 | <span className="bin-code">{bin.rack}-{bin.slot}</span> | |
| 16 | 23 | <span className="bin-label">{bin.producer} · {bin.vintage}</span> | |
| 17 | 24 | </button> | |
| 18 | 25 | ); | |
| 19 | 26 | }; | |
| 20 | 27 | ||
| 21 | 28 | return ( | |
| 22 | - | <List height={720} width="100%" itemCount={bins.length} itemSize={52}> | |
| 23 | - | {Row} | |
| 24 | - | </List> | |
| 29 | + | <List | |
| 30 | + | height={720} | |
| 31 | + | width="100%" | |
| 32 | + | itemCount={bins.length} | |
| 33 | + | itemSize={52} | |
| 34 | + | initialScrollOffset={Number.isFinite(initialOffset) ? initialOffset : 0} | |
| 35 | + | onScroll={onScroll} | |
| 36 | + | > | |
| 37 | + | {Row} | |
| 38 | + | </List> | |
| 25 | 39 | ); | |
| 26 | 40 | } |