MMd. Sabit Islam Bhuiya

Implement virtual scrolling in react from scratch with custom hook.

Virtual Scrolling

Virtual scrolling is a necessary concept when we have a big list of data and we want to render the list. If we render the whole list at once it will decrease our app performance. So we use a concept called virtual scrolling to render only those items that are visible on the current screen.

🛠 The Concept

Most of the time we will use a library to use virtual scrolling. But in this article we will learn how we can implement our a virtual scroll from scratch.

If we think about the virtual scrolling concept, we can easily identify that it is nothing but a sliding window on the actual list that slides on left or right on the scroll event. when our scrollbar reaches the end the sliding window slides on right and when we scroll back and reaches the start, the sliding window slides on left.


💻 The Custom Hook Implementation

So from this simple concept we can write a custom hook that will take our actual list and return the sliding window that we call the virtual list. The implementation may look like this:

const useVirtualScroll = (  
  ref: RefObject<HTMLElement>,  
  array: any[],  
  childHeight = 50  
) => {  
  const [slidingWindow, setSlidingWindow] = useState<typeof array>([]);  
  const [startIndex, setStartIndex] = useState(0);  
  const [endIndex, setEndIndex] = useState(0);  
  
  useEffect(() => {  
    if (ref.current) {  
      let startIndex = 0;  
      const parentHeight = ref.current.clientHeight;  
      const size = Math.ceil(parentHeight / childHeight) * 2;  
      let endIndex = startIndex + size;  
      const updateState = () => {  
        setSlidingWindow(array.slice(startIndex, endIndex));  
        setStartIndex(startIndex);  
        setEndIndex(endIndex);  
      };  
  
      const incrementIndex = () => {  
        endIndex = endIndex < array.length - 1 ? endIndex + 1 : endIndex;  
        startIndex = endIndex - size;  
      };  
  
      const decrementIndex = () => {  
        startIndex = startIndex > 0 ? startIndex - 1 : startIndex;  
        endIndex = startIndex + size;  
      };  
  
      const onScroll = () => {  
        const scrollTop = ref.current!.scrollTop;  
        const isOnTop = scrollTop < childHeight;  
        const isOnBottom =  
          ref.current!.scrollHeight - scrollTop <  
          ref.current!.clientHeight + childHeight;  
        if (isOnBottom && endIndex < array.length) {  
          incrementIndex();  
          updateState();  
          ref.current!.scrollTop -= childHeight;  
        } else if (isOnTop && startIndex > 0) {  
          decrementIndex();  
          updateState();  
          ref.current!.scrollTop += childHeight;  
        }  
      };  
  
      updateState();  
      ref.current.addEventListener("scroll", onScroll);  
      return () => {  
          ref.current!.removeEventListener("scroll", onScroll);  
      };  
    }  
  }, [ref.current]);  
  
  return [slidingWindow, startIndex, endIndex];  
};

Note: Here we need the ref of the container element that has a fixed height and a overflow scroll. We also need a size of the item elements to calculate how much items we want to render at a time. Additionally, we return the start and end index so that we can calculate the index of a item in the actual array.


🚀 Usage Example

Let’s see how we can use the hook:

function App() {  
  const arr = useMemo(() => new Array(2000).fill(0).map((_, i) => i), []);  
  const ref = useRef<HTMLUListElement>(null);  
  const [state] = useVirtualScroll(ref, arr, 90);  
  
  return (  
    <>  
      <ul  
        ref={ref}  
        style={{  
          height: "400px",  
          overflowY: "scroll",  
        }}  
      >  
        {(state as any[]).map((val:number,key:number) => (  
          <li  
            style={{  
              padding: "20px",  
              margin: "20px",  
              border: "1px solid black",  
              height: "90px",  
            }}  
            key={key}  
          >  
            {val}  
          </li>  
        ))}  
      </ul>  
    </>  
  );  
}

Here we have a list of 2000 elements. But by using our custom hook we will render only a few elements at a time.

So, that’s it. We just created our own simple custom hook for virtual scrolling.

Thank you for reading this article! Happy coding!