0

Recently I came across reat-window library, it is great and handy. This make me think how it is built, so i decided to give it a try and build my own virtualized list

However the list can only scroll to item "60". Wondering what is wrong with my index calculation, and look at code from react-window, seems like we are doing similar thing src1 src2.

Any idea what is wrong with my index calculation

import React, { useMemo, useCallback, useState } from 'react';

const ListItem = ({ val, height }) => {
  return <li style={{ height: `${height}px` }}>{val}</li>;
};

const ITEM_HEIGHT = 20;
const CONTAINER_HEIGHT = 300;
const BUFFER = Math.ceil(CONTAINER_HEIGHT / ITEM_HEIGHT);

export const App = () => {
  const [scrollTop, setScrollTop] = useState(0);

  const allItems = useMemo(() => {
    const items = [];
    for (let i = 0; i < 500; i++) {
      items.push(<ListItem val={i} height={ITEM_HEIGHT} />)
    }
    return items;
  }, [ITEM_HEIGHT]);

  const onScrollHandler = useCallback((event) => {
    setScrollTop(event.currentTarget.scrollTop);
  }, [allItems, setScrollTop]);

  // start index = (total scrolled pixel / row height) - buffer
  const start = Math.max(0, Math.floor(scrollTop / ITEM_HEIGHT) - BUFFER);
  // end index   = (total scrolled pixel + list height) / row height + buffer
  const end = Math.min(allItems.length - 1, Math.ceil((scrollTop + CONTAINER_HEIGHT) / ITEM_HEIGHT) + BUFFER);
  const itemsToBeRender = allItems.slice(start, end);

  return (
    <div
      style={{ overflowY: "scroll" }}
      onScroll={onScrollHandler}>
      <ul
        style={{ height: `${CONTAINER_HEIGHT}px` }}
      >
        {itemsToBeRender}
      </ul>
    </div>
  );
};
Xiaomin Wu
  • 3,621
  • 1
  • 15
  • 14

1 Answers1

0

I did some google search and come across this article. After adding "position" attribute everything work just expected.

import React, { useMemo, useCallback, useState } from 'react';

const ITEM_HEIGHT = 20;
const CONTAINER_HEIGHT = 300;
const BUFFER = Math.ceil(CONTAINER_HEIGHT / ITEM_HEIGHT);

export const App = () => {
  const [scrollTop, setScrollTop] = useState(0);

  const allItems = useMemo(() => {
    const items = [];
    for (let i = 0; i < 200; i++) {
      const style = {
        height: `${ITEM_HEIGHT}px`,
        position: "absolute",
        top: `${i * ITEM_HEIGHT}px`,
      };
      items.push(<li key={i} style={style} >{i}</li>)
    }
    return items;
  }, [ITEM_HEIGHT]);

  const onScrollHandler = useCallback((event) => {
    setScrollTop(event.currentTarget.scrollTop);
  }, [allItems, setScrollTop]);

  // fake we have a lots of item, so that the scroll bar will always show up
  const fullHeight = allItems.length * ITEM_HEIGHT;
  const start = Math.max(0, Math.floor(scrollTop / ITEM_HEIGHT) - BUFFER);
  const end = Math.min(
    allItems.length - 1,
    Math.floor((scrollTop + CONTAINER_HEIGHT) / ITEM_HEIGHT) + BUFFER);

  const itemsToBeRender = allItems.slice(start, end + 1);

  return (
    <div
      style={{ overflowY: "scroll", height: `${CONTAINER_HEIGHT}px` }}
      onScroll={onScrollHandler}>
      <ul
        style={{ height: `${fullHeight}px`, position: "relative" }}
      >
        {itemsToBeRender}
      </ul>
    </div>
  );
};
Xiaomin Wu
  • 3,621
  • 1
  • 15
  • 14