Hone logo
Hone
Problems

React Virtualized List Component Challenge

This challenge focuses on building a performant and efficient virtualized list component in React using TypeScript. As the number of items in a list grows, rendering all of them at once can lead to significant performance degradation and memory issues. A virtualized list only renders the items that are currently visible in the viewport, drastically improving performance for large datasets.

Problem Description

Your task is to create a reusable React component called VirtualizedList that efficiently renders a list of items. This component should dynamically render only the items that are currently visible within the container, along with a small buffer above and below to ensure smooth scrolling.

Key Requirements:

  1. Dynamic Rendering: The component must only render the subset of items that are currently within the viewport, plus a configurable buffer.
  2. Props:
    • items: An array of data to be displayed in the list. Each item can be of any type, but the renderItem prop will dictate how it's displayed.
    • itemHeight: The fixed height of each individual list item in pixels. This is crucial for calculating which items are visible.
    • containerHeight: The height of the scrollable container in pixels.
    • renderItem: A function that takes an item from the items array and its index, and returns a React element to render for that item.
    • bufferSize: An optional number (defaults to 3) representing the number of extra items to render above and below the visible items.
  3. Scrolling Behavior: The component should correctly update which items are rendered as the user scrolls the container.
  4. TypeScript: The component and its props must be defined using TypeScript.

Expected Behavior:

When provided with a large list of items, the VirtualizedList should render smoothly, with no noticeable lag during scrolling. The total number of DOM elements rendered should be proportional to containerHeight / itemHeight + 2 * bufferSize, not the total number of items in the items array.

Edge Cases to Consider:

  • Empty items array.
  • containerHeight being smaller than itemHeight.
  • Scrolling to the very beginning or end of the list.

Examples

Example 1: Basic Usage

// Assume `App.tsx` or similar
import React from 'react';
import VirtualizedList from './VirtualizedList'; // Assuming your component is in VirtualizedList.tsx

const data = Array.from({ length: 1000 }, (_, i) => `Item ${i + 1}`);

const App: React.FC = () => {
  return (
    <div style={{ height: '400px', overflowY: 'auto', border: '1px solid black' }}>
      <VirtualizedList
        items={data}
        itemHeight={40}
        containerHeight={400}
        renderItem={(item, index) => (
          <div style={{ height: '40px', borderBottom: '1px solid #eee', padding: '10px' }}>
            {item}
          </div>
        )}
      />
    </div>
  );
};

export default App;

Input:

  • items: An array of 1000 strings, e.g., ['Item 1', 'Item 2', ..., 'Item 1000'].
  • itemHeight: 40 pixels.
  • containerHeight: 400 pixels.
  • renderItem: A function that renders a div with the item's text and a fixed height of 40px.

Output:

The VirtualizedList component will render a scrollable container. Initially, only the first ~10-12 items (400px / 40px + buffer) will be rendered in the DOM. As the user scrolls, the component will dynamically update the rendered items to maintain this efficient rendering strategy. The scrollbar will represent the full height of all 1000 items.

Example 2: Using Buffer Size

// Inside your component
const data = Array.from({ length: 500 }, (_, i) => `Data point ${i}`);

return (
  <div style={{ height: '300px', overflowY: 'auto', border: '1px solid blue' }}>
    <VirtualizedList
      items={data}
      itemHeight={50}
      containerHeight={300}
      bufferSize={5} // More buffer
      renderItem={(item, index) => (
        <div style={{ height: '50px', display: 'flex', alignItems: 'center', paddingLeft: '15px' }}>
          {item}
        </div>
      )}
    />
  </div>
);

Input:

  • items: An array of 500 strings.
  • itemHeight: 50 pixels.
  • containerHeight: 300 pixels.
  • bufferSize: 5.

Output:

The component will render approximately 300 / 50 + 2 * 5 = 6 + 10 = 16 items at any given time. The increased buffer size ensures that even with rapid scrolling, more items are already in the DOM, potentially leading to a smoother experience for very fast scroll gestures, at the cost of slightly more DOM elements.

Constraints

  • items.length: Can be between 0 and 1,000,000.
  • itemHeight: Must be a positive integer between 10 and 200 (inclusive).
  • containerHeight: Must be a positive integer between 50 and 1000 (inclusive).
  • bufferSize: Must be a non-negative integer between 0 and 10 (inclusive).
  • The renderItem function should be pure and not cause side effects.
  • The component should aim for efficient DOM manipulation. Avoid unnecessary re-renders.

Notes

  • You will need to manage the scroll position of the container and calculate which items fall within the visible range.
  • Consider using React.useRef to get a reference to the scrollable container.
  • The total height of all items will be items.length * itemHeight. This value is crucial for setting the scrollHeight of the container element.
  • You'll need to calculate the startIndex and endIndex of the items to render based on the scroll position, containerHeight, itemHeight, and bufferSize.
  • The rendered items should be absolutely positioned within a parent container to allow for precise placement based on their index.
Loading editor...
typescript