import { useState, useRef } from "react"; // ============================================= // Generate a large dataset — 10,000 items (DO NOT MODIFY) // ============================================= interface ListItem { id: number; name: string; email: string; role: string; } const ROLES = [ "Engineer", "Designer", "Product Manager", "Data Scientist", "DevOps", "QA Engineer", "Tech Lead", "Intern", ]; const ITEMS: ListItem[] = Array.from({ length: 10_000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: ROLES[i % ROLES.length], })); // ============================================= // Constants (DO NOT MODIFY) // ============================================= const ITEM_HEIGHT = 60; // px — fixed height per row const CONTAINER_HEIGHT = 500; // px — visible viewport height const OVERSCAN = 5; // extra rows above/below viewport // ============================================= // TODO: Implement Virtualized List // ============================================= // // The Problem: // Rendering 10,000 DOM nodes is slow. A virtualized list only // mounts the ~10 rows visible in the viewport (plus a small // buffer), giving the illusion of a full list via a tall // scrollable container. // // Architecture: // ┌─ Scroll container (fixed height, overflow: auto) ──────┐ // │ ┌─ Inner div (height = totalItems * ITEM_HEIGHT) ───┐ │ // │ │ │ │ // │ │ ← empty space (scrollbar sees full height) │ │ // │ │ │ │ // │ │ ┌─ Visible window (translated down) ───────────┐ │ │ // │ │ │ Row startIndex │ │ │ // │ │ │ Row startIndex + 1 │ │ │ // │ │ │ ... │ │ │ // │ │ │ Row endIndex │ │ │ // │ │ └──────────────────────────────────────────────┘ │ │ // │ │ │ │ // │ └────────────────────────────────────────────────────┘ │ // └─────────────────────────────────────────────────────────┘ // // Steps: // 1. Track scrollTop via onScroll on the container // 2. Calculate which rows are visible: // startIndex = Math.floor(scrollTop / ITEM_HEIGHT) - OVERSCAN // endIndex = startIndex + visibleCount + OVERSCAN * 2 // 3. Slice ITEMS[startIndex..endIndex] // 4. Position the visible rows using translateY(startIndex * ITEM_HEIGHT) // 5. The inner div's height creates the scrollbar // // Hints: // - totalHeight = ITEMS.length * ITEM_HEIGHT // - visibleCount = Math.ceil(CONTAINER_HEIGHT / ITEM_HEIGHT) // - Clamp startIndex to >= 0, endIndex to <= ITEMS.length - 1 // - offsetY = startIndex * ITEM_HEIGHT // - Use a ref on the container to read scrollTop export default function App() { // TODO: Track scroll position // const [scrollTop, setScrollTop] = useState(0); // const containerRef = useRef<HTMLDivElement>(null); // ============================================= // TODO: Calculate derived values // ============================================= // const totalHeight = ITEMS.length * ITEM_HEIGHT; // const visibleCount = Math.ceil(CONTAINER_HEIGHT / ITEM_HEIGHT); // // const startIndex = Math.max(0, Math.floor(scrollTop / ITEM_HEIGHT) - OVERSCAN); // const endIndex = Math.min(ITEMS.length - 1, startIndex + visibleCount + OVERSCAN * 2); // // const visibleItems = ITEMS.slice(startIndex, endIndex + 1); // const offsetY = startIndex * ITEM_HEIGHT; // Placeholder — remove once you implement the above const totalHeight = ITEMS.length * ITEM_HEIGHT; const visibleItems: ListItem[] = []; const offsetY = 0; // ============================================= // TODO: Implement scroll handler // ============================================= // const handleScroll = () => { // if (!containerRef.current) return; // setScrollTop(containerRef.current.scrollTop); // }; return ( <div style={{ maxWidth: 640, margin: "0 auto", padding: 24, fontFamily: "system-ui" }}> <h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 4 }}> Virtualized List </h2> <p style={{ fontSize: 14, color: "#666", marginBottom: 24 }}> Render {ITEMS.length.toLocaleString()} items efficiently by only mounting the rows visible in the viewport. Implement the virtualization logic — the dataset and row template are ready. </p> {/* Stats bar */} <div style={{ display: "flex", justifyContent: "space-between", padding: "10px 14px", background: "#f9fafb", border: "1px solid #e5e7eb", borderRadius: 8, fontSize: 13, marginBottom: 16, }}> <span>Total items: <strong>{ITEMS.length.toLocaleString()}</strong></span> <span> Rendered: <strong>{visibleItems.length}</strong> </span> </div> {/* ============================================= */} {/* TODO: Scrollable container */} {/* ============================================= */} {/* Attach: ref={containerRef} */} {/* onScroll={handleScroll} */} <div // ref={containerRef} // onScroll={handleScroll} style={{ height: CONTAINER_HEIGHT, overflow: "auto", border: "1px solid #e5e7eb", borderRadius: 8, position: "relative", background: "#fafafa", }} > {/* Inner div — creates the full scrollable height */} <div style={{ height: totalHeight, position: "relative" }}> {/* =========================================== */} {/* TODO: Visible items window */} {/* =========================================== */} {/* Position this div at offsetY using */} {/* transform: translateY(${offsetY}px) */} {/* Then render only visibleItems inside it */} <div style={{ position: "absolute", left: 0, right: 0, // TODO: transform: `translateY(${offsetY}px)`, }} > {visibleItems.length === 0 ? ( <div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: CONTAINER_HEIGHT, color: "#999", fontSize: 14, }}> Implement the virtualization logic to see rows here </div> ) : ( visibleItems.map((item) => ( <div key={item.id} style={{ height: ITEM_HEIGHT, display: "flex", alignItems: "center", padding: "0 16px", borderBottom: "1px solid #f3f4f6", }} > {/* Avatar */} <div style={{ width: 32, height: 32, borderRadius: "50%", background: "#e5e7eb", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 11, fontWeight: 600, color: "#666", flexShrink: 0, }}> {item.id} </div> {/* Info */} <div style={{ marginLeft: 12, minWidth: 0 }}> <div style={{ fontSize: 13, fontWeight: 600, color: "#111" }}> {item.name} </div> <div style={{ fontSize: 11, color: "#999" }}> {item.email} · {item.role} </div> </div> </div> )) )} </div> </div> </div> {/* Row template preview */} <div style={{ marginTop: 24, marginBottom: 8, fontSize: 11, color: "#999", textTransform: "uppercase", letterSpacing: 1, fontWeight: 600 }}> Row template preview </div> <div style={{ border: "1px dashed #d1d5db", borderRadius: 8, height: ITEM_HEIGHT, display: "flex", alignItems: "center", padding: "0 16px", }}> <div style={{ width: 32, height: 32, borderRadius: "50%", background: "#e5e7eb", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 11, fontWeight: 600, color: "#666", }}> 1 </div> <div style={{ marginLeft: 12 }}> <div style={{ fontSize: 13, fontWeight: 600, color: "#111" }}>User 1</div> <div style={{ fontSize: 11, color: "#999" }}>user1@example.com · Engineer</div> </div> </div> {/* Hint */} <div style={{ marginTop: 24, padding: 16, background: "#f9fafb", borderLeft: "4px solid #111", borderRadius: "0 8px 8px 0", fontSize: 13, color: "#555", }}> <strong>How it works:</strong> The outer div scrolls over a tall inner div (total height = {ITEMS.length} × {ITEM_HEIGHT}px), but you only render a small window of items positioned with <code>translateY</code>. <ul style={{ margin: "8px 0 0 16px", padding: 0, lineHeight: 1.8 }}> <li><code>startIndex = Math.floor(scrollTop / ITEM_HEIGHT) - OVERSCAN</code></li> <li><code>visibleCount = Math.ceil(CONTAINER_HEIGHT / ITEM_HEIGHT)</code></li> <li>Slice items from startIndex to endIndex</li> <li>Offset the visible window: <code>translateY(startIndex * ITEM_HEIGHT)</code></li> <li>Listen to <code>onScroll</code> to recalculate on every scroll</li> </ul> </div> </div> ); }