Official Solution
import React, { useMemo, useRef, useState } from 'react';
const items = Array.from({ length: 12000 }, (_, i) => ({ id: i + 1, label: 'meetcode item ' + (i + 1) }));
const rowHeight = 28;
const viewportHeight = 260;
const overscan = 6;
export default function App() {
const [scrollTop, setScrollTop] = useState(0);
const rafRef = useRef(0);
const range = useMemo(() => {
const start = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
const visible = Math.ceil(viewportHeight / rowHeight) + overscan * 2;
const end = Math.min(items.length, start + visible);
return { start, end };
}, [scrollTop]);
const slice = useMemo(() => items.slice(range.start, range.end), [range]);
function onScroll(e) {
const next = e.currentTarget.scrollTop;
if (rafRef.current) cancelAnimationFrame(rafRef.current);
rafRef.current = requestAnimationFrame(() => setScrollTop(next));
}
return (
<div style={{ padding: 16, width: 560 }}>
<h2 style={{ marginTop: 0 }}>Windowed list</h2>
<div style={{ color: '#555' }}>Rendering {slice.length} of {items.length}</div>
<div onScroll={onScroll} style={{ marginTop: 10, height: viewportHeight, overflow: 'auto', border: '1px solid #eee', borderRadius: 14 }}>
<div style={{ height: items.length * rowHeight, position: 'relative' }}>
<div style={{ position: 'absolute', top: range.start * rowHeight, left: 0, right: 0 }}>
{slice.map((x) => (
<div key={x.id} style={{ height: rowHeight, display: 'flex', alignItems: 'center', paddingLeft: 12, borderBottom: '1px solid #f3f4f6' }}>
{x.label}
</div>
))}
</div>
</div>
</div>
</div>
);
}
No comments yet. Start the discussion!