React: Virtualized List From Scratch
Tags: react, nextjs
Published: 2023-04-06 08:22:02

A virtualized list is a list that renders only a subset of its items and replaces the invisible items with placeholders. The virtualization technique can significantly improve the performance of rendering long lists by reducing the number of DOM nodes that need to be created and updated.

To build a virtualized list from scratch, we will follow these steps:

**1. Create a component for the list.**

The first step in building a virtualized list is to create a component that represents the list. We can create a functional component that takes an array of items as a prop.

```
function VirtualizedList({ items }) {
  return (
    <div>
      {items.map((item) => (
        <div key={item.id}>{item.content}</div>
      ))}
    </div>
  );
}
```

In this example, we use the map method to iterate over the items array and render each item as a div element. We also assign a unique key to each item to help React identify them.

---

**2. Render a subset of the list items.**

The second step in building a virtualized list is to render only a subset of the list items. To achieve this, we need to know the height of each item and the height of the container that holds the list.

```
function VirtualizedList({ items, itemHeight, containerHeight }) {
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(
    startIndex + Math.ceil(containerHeight / itemHeight),
    items.length
  );
  const visibleItems = items.slice(startIndex, endIndex);

return (
    <div style={{ height: `${items.length * itemHeight}px` }}>
      <div
        style={{
          position: "relative",
          height: `${visibleItems.length * itemHeight}px`,
          top: `${startIndex * itemHeight}px`,
        }}
      >
        {visibleItems.map((item) => (
          <div key={item.id} style={{ height: `${itemHeight}px` }}>
            {item.content}
          </div>
        ))}
      </div>
    </div>
  );
}
```

In this updated example, we added the itemHeight and containerHeight props to the component. We also calculated the startIndex and endIndex based on the scrollTop, itemHeight, and containerHeight. The startIndex represents the index of the first visible item, and the endIndex represents the index of the last visible item.

We then used the slice method to extract only the visible items from the items array. Finally, we rendered the visible items as div elements with a height of itemHeight. We also positioned the container using the top property, which is equal to startIndex * itemHeight.

---

**3. Replace the invisible items with placeholders.**

The third step in building a virtualized list is to replace the invisible items with placeholders. This step is essential to maintain the correct height of the list and prevent the visible items from jumping when scrolling.

```
function VirtualizedList({ items, itemHeight, containerHeight }) {
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(
    startIndex + Math.ceil(containerHeight / itemHeight),
    items.length
  );
  const visibleItems = items.slice(startIndex, endIndex);
  const invisibleItemsHeight =
    (startIndex + visibleItems.length - endIndex) * itemHeight;

  return (
    <div style={{ height: `${items.length * itemHeight}px` }}>
      <div
        style={{
          position: "relative",
          height: `${visibleItems.length * itemHeight}px`,
          top: `${startIndex * itemHeight}px`,
        }}
      >
        {visibleItems.map((item) => (
          <div key={item.id} style={{ height: `${itemHeight}px` }}>
            {item.content}
          </div>
        ))}
      </div>
      <div style={{ height: `${invisibleItemsHeight}px` }} />
    </div>
  );
}
```

In this updated example, we calculated the `invisibleItemsHeight` by subtracting the height of the visible items from the height of the container. We then added an empty `div` element with a height equal to `invisibleItemsHeight` to replace the invisible items.

---

**4. Add scrolling functionality to the list.**

The fourth and final step in building a virtualized list is to add scrolling functionality to the list. We can achieve this by adding an event listener to the container element and updating the `scrollTop` state whenever the user scrolls.

```
function VirtualizedList({ items, itemHeight, containerHeight }) {
  const [scrollTop, setScrollTop] = useState(0);
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(
    startIndex + Math.ceil(containerHeight / itemHeight),
    items.length
  );
  const visibleItems = items.slice(startIndex, endIndex);
  const invisibleItemsHeight = (startIndex + visibleItems.length - endIndex) * itemHeight;
  const handleScroll = (event) => {
    setScrollTop(event.target.scrollTop);
  };
  return (
    <div
      style={{ height: `${containerHeight}px`, overflowY: "scroll" }}
      onScroll={handleScroll}
    >
      <div style={{ height: `${items.length * itemHeight}px` }}>
        <div
          style={{
            position: "relative",
            height: `${visibleItems.length * itemHeight}px`,
            top: `${startIndex * itemHeight}px`,
          }}
        >
          {visibleItems.map((item) => (
            <div key={item.id} style={{ height: `${itemHeight}px` }}>
              {item.content}
            </div>
          ))}
        </div>
        <div style={{ height: `${invisibleItemsHeight}px` }} />
      </div>
    </div>
  );
}
```
In this final example, we added the scrollTop state using the useState hook. We also added the onScroll event listener to the container element and called the handleScroll function to update the scrollTop state whenever the user scrolls.

We also added the overflowY property to the container element to enable scrolling. Finally, we wrapped the entire list inside the container element with a height equal to containerHeight.

[Reference: taken from here](https://asimzaidi.medium.com/)