Rendering large lists without virtualization causing slow UI

Loading

Rendering large lists without virtualization is a common performance pitfall that can lead to sluggish UI and poor user experience. Here’s how to properly handle large datasets in React applications.

The Core Problem

Problematic implementation:

function BigList({ items }) { // Could be thousands of items
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          <ComplexListItem item={item} />
        </li>
      ))}
    </ul>
  );
}

Issues this causes:

  • Slow initial render
  • Janky scrolling
  • High memory usage
  • Poor responsiveness

Solution: Virtualization

1. Using React-Window (Recommended)

import { FixedSizeList as List } from 'react-window';

function BigList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      <ComplexListItem item={items[index]} />
    </div>
  );

  return (
    <List
      height={600}
      width={300}
      itemCount={items.length}
      itemSize={100} // Height of each row
    >
      {Row}
    </List>
  );
}

2. Using React-Virtualized

import { List } from 'react-virtualized';

function BigList({ items }) {
  const rowRenderer = ({ key, index, style }) => (
    <div key={key} style={style}>
      <ComplexListItem item={items[index]} />
    </div>
  );

  return (
    <List
      width={300}
      height={600}
      rowCount={items.length}
      rowHeight={100}
      rowRenderer={rowRenderer}
    />
  );
}

Alternative Approaches

1. Pagination

function PaginatedList({ items }) {
  const [page, setPage] = useState(1);
  const itemsPerPage = 50;

  const paginatedItems = items.slice(
    (page - 1) * itemsPerPage,
    page * itemsPerPage
  );

  return (
    <div>
      <ul>
        {paginatedItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      <Pagination
        currentPage={page}
        totalItems={items.length}
        itemsPerPage={itemsPerPage}
        onPageChange={setPage}
      />
    </div>
  );
}

2. Infinite Loading

import { useInfiniteQuery } from 'react-query';

function InfiniteList() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage
  } = useInfiniteQuery(
    'items',
    ({ pageParam = 0 }) => fetchItems(pageParam),
    {
      getNextPageParam: (lastPage) => lastPage.nextPage,
    }
  );

  return (
    <ul>
      {data.pages.map(page => (
        page.items.map(item => (
          <li key={item.id}>{item.name}</li>
        ))
      ))}
      {hasNextPage && (
        <button
          onClick={() => fetchNextPage()}
          disabled={isFetchingNextPage}
        >
          {isFetchingNextPage ? 'Loading...' : 'Load More'}
        </button>
      )}
    </ul>
  );
}

Performance Optimization Techniques

1. Memoize List Items

const MemoizedListItem = React.memo(function ListItem({ item }) {
  return <ComplexListItem item={item} />;
});

function BigList({ items }) {
  return (
    <ul>
      {items.map(item => (
        <MemoizedListItem key={item.id} item={item} />
      ))}
    </ul>
  );
}

2. Windowing with Custom Implementation

function VirtualList({ items, itemHeight, containerHeight }) {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef();

  const visibleCount = Math.ceil(containerHeight / itemHeight);
  const startIndex = Math.floor(scrollTop / itemHeight);
  const visibleItems = items.slice(startIndex, startIndex + visibleCount);

  const handleScroll = () => {
    setScrollTop(containerRef.current.scrollTop);
  };

  return (
    <div 
      ref={containerRef}
      style={{ height: containerHeight, overflow: 'auto' }}
      onScroll={handleScroll}
    >
      <div style={{ height: `${items.length * itemHeight}px` }}>
        {visibleItems.map((item, index) => (
          <div 
            key={item.id}
            style={{
              position: 'absolute',
              top: `${(startIndex + index) * itemHeight}px`,
              height: `${itemHeight}px`
            }}
          >
            <ComplexListItem item={item} />
          </div>
        ))}
      </div>
    </div>
  );
}

Best Practices

  1. Always use virtualization for lists with more than 100 items
  2. Set proper item sizes for accurate scroll behavior
  3. Memoize expensive item components to prevent unnecessary re-renders
  4. Consider pagination when virtualization isn’t enough
  5. Use CSS containment for complex list items:
   .list-item {
     contain: content;
   }
  1. Avoid inline styles/functions in list items that prevent memoization
  2. Measure performance with React DevTools Profiler

Key Takeaways

  1. Virtualization libraries (react-window, react-virtualized) solve this problem
  2. Only render visible items to maintain performance
  3. Pagination/infinite loading are good alternatives
  4. Proper key assignment remains crucial
  5. Memoization helps with complex list items

Proper handling of large lists ensures your React application remains performant and responsive regardless of dataset size. Virtualization should be your default approach when dealing with lists that might contain hundreds or thousands of items.

Leave a Reply

Your email address will not be published. Required fields are marked *