Technology

React Items: Infinite Scroll & Virtualization Tutorial

A comprehensive guide to handling large datasets in React applications. Learn how to implement smooth infinite scroll functionality and lightning-fast virtualization using Next.js, TypeScript, and The Movie Database API.

25 min read
Published

Infinite Scroll Tutorial Code

Complete source code for the infinite scroll tutorial with TMDB API integration and responsive design.

View on GitHub

Virtualization Tutorial Code

Complete source code for the React Window virtualization tutorial with four different examples.

View on GitHub

Part 1: Building Infinite Scroll with Next.js and TMDB API

Introduction

Infinite scroll has become a cornerstone of modern web applications, providing users with seamless content discovery experiences. From social media feeds to e-commerce product listings, this pattern eliminates the need for traditional pagination while keeping users engaged with continuous content loading.

This tutorial demonstrates how to build a robust infinite scroll implementation using Next.js 16, TypeScript, and The Movie Database (TMDB) API. We'll cover everything from basic scroll detection to advanced performance optimizations, ensuring your application delivers a smooth user experience across all devices.

What is Infinite Scroll?

Infinite scroll is a user interface pattern that automatically loads additional content as users approach the end of the current page. This technique creates a continuous browsing experience without requiring users to click through pagination controls.

Traditional Pagination

Page 1 → Click Next → Page 2
Manual navigation required

Users must actively navigate between pages

Infinite Scroll

Scroll down → Auto-load → More content appears → Seamless experience

Content loads automatically as users scroll

Key Features

Our infinite scroll implementation includes several essential features that ensure optimal performance and user experience:

Automatic Loading

Detects when users approach the bottom and automatically fetches more content without user intervention.

Responsive Design

Mobile-first responsive grid that adapts from 1 column on mobile to 4 columns on desktop screens.

Performance Optimized

Debounced scroll events and efficient state management prevent excessive API calls and ensure smooth scrolling.

Error Handling

Graceful error handling with user-friendly messages and loading states for better user experience.

Tech Stack

This tutorial uses modern web development technologies to create a production-ready infinite scroll implementation:

Next.js 16: React framework with App Router for modern web applications
TypeScript: Type safety and enhanced developer experience
Tailwind CSS: Utility-first styling and responsive design
TMDB API: The Movie Database API for rich movie data
Axios & Lodash: HTTP client and utility functions for optimization

Project Structure

The application follows a clean, organized structure that separates concerns and promotes maintainability:

src/app/
├── page.tsx                    # Main page with infinite scroll logic
├── layout.tsx                  # Root layout
├── globals.css                 # Global styles
└── components/
    ├── MovieCard.tsx           # Individual movie card component
    └── MovieCardSkeleton.tsx   # Loading skeleton component

Core Implementation

State Management

The infinite scroll functionality relies on React hooks to manage application state efficiently. Here's how we structure our state:

const [page, setPage] = useState(1);           // Current page number
const [data, setData] = useState([]);           // Array of movie data
const [loading, setLoading] = useState(false);  // Loading state
const [error, setError] = useState(null);       // Error handling

Scroll Detection

The heart of infinite scroll is detecting when users approach the bottom of the page. Our implementation triggers loading when users are within 300 pixels of the bottom:

const handleScroll = () => {
  if (
    document.body.scrollHeight - 300 <
    window.scrollY + window.innerHeight
  ) {
    setPage((prevPage) => prevPage + 1);
  }
};

Performance Optimization

To prevent excessive API calls and ensure smooth scrolling, we implement debouncing using Lodash. This limits how frequently the scroll handler can execute:

import { debounce } from 'lodash';

const debouncedHandleScroll = debounce(handleScroll, 500);

useEffect(() => {
  window.addEventListener('scroll', debouncedHandleScroll);
  return () => window.removeEventListener('scroll', debouncedHandleScroll);
}, []);

API Integration

We use The Movie Database (TMDB) API to fetch popular movies with pagination support. The API integration includes proper authentication and error handling:

const fetchMovies = async () => {
  try {
    setLoading(true);
    const response = await axios.get(
      `https://api.themoviedb.org/3/movie/popular?language=en-US&page=${page}`,
      {
        headers: {
          Authorization: `Bearer ${process.env.NEXT_PUBLIC_TMDB_READ_ACCESS_TOKEN}`
        }
      }
    );
    setData(prevData => [...prevData, ...response.data.results]);
  } catch (err) {
    setError('Failed to load movies');
  } finally {
    setLoading(false);
  }
};

Component Architecture

MovieCard Component

The MovieCard component displays individual movie information with poster images, titles, descriptions, and ratings. It uses responsive design principles to adapt to different screen sizes:

interface Movie {
  id: number;
  title: string;
  overview: string;
  poster_path: string;
  vote_average: number;
}

const MovieCard: React.FC<{ movie: Movie }> = ({ movie }) => {
  return (
    <div className="bg-gray-800 rounded-lg overflow-hidden shadow-lg hover:shadow-xl transition-shadow">
      <img 
        src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`}
        alt={movie.title}
        className="w-full h-64 object-cover"
      />
      <div className="p-4">
        <h3 className="text-lg font-semibold mb-2">{movie.title}</h3>
        <p className="text-gray-400 text-sm mb-2 line-clamp-3">
          {movie.overview}
        </p>
        <div className="flex items-center">
          <span className="text-yellow-400">★</span>
          <span className="ml-1 text-sm">{movie.vote_average.toFixed(1)}</span>
        </div>
      </div>
    </div>
  );
};

Loading Skeleton

The MovieCardSkeleton component provides visual feedback during loading states, maintaining layout stability and improving perceived performance:

const MovieCardSkeleton: React.FC = () => {
  return (
    <div className="bg-gray-800 rounded-lg overflow-hidden shadow-lg animate-pulse">
      <div className="w-full h-64 bg-gray-700"></div>
      <div className="p-4">
        <div className="h-6 bg-gray-700 rounded mb-2"></div>
        <div className="h-4 bg-gray-700 rounded mb-1"></div>
        <div className="h-4 bg-gray-700 rounded mb-1"></div>
        <div className="h-4 bg-gray-700 rounded w-3/4"></div>
      </div>
    </div>
  );
};

Setup and Configuration

Getting started with the infinite scroll tutorial is straightforward. Follow these steps to set up your development environment:

  1. 1
    Clone the repository:
    git clone https://github.com/audoir/infinite-scroll-tutorial.git
  2. 2
    Install dependencies:
    npm install
  3. 3
    Environment setup:

    Create a .env.local file and add your TMDB API key:

    NEXT_PUBLIC_TMDB_READ_ACCESS_TOKEN=your_token_here

    Get your API key from The Movie Database

  4. 4
    Start the development server:
    npm run dev

    Open http://localhost:3000 in your browser

Best Practices and Considerations

Performance Optimization

Implementing infinite scroll requires careful attention to performance. Here are key strategies we employ:

  • Debouncing: Limit scroll event frequency to prevent excessive API calls
  • Loading States: Provide visual feedback during data fetching
  • Error Boundaries: Graceful error handling for network failures
  • Memory Management: Consider implementing virtualization for large datasets

User Experience

A well-implemented infinite scroll should feel natural and responsive:

  • Smooth Transitions: Use CSS transitions for loading states
  • Skeleton Loading: Maintain layout stability during loading
  • Responsive Design: Adapt to different screen sizes and orientations
  • Accessibility: Ensure keyboard navigation and screen reader support

SEO Considerations

Infinite scroll can impact SEO if not implemented carefully. Consider these strategies:

  • Server-Side Rendering: Ensure initial content is rendered on the server
  • URL Updates: Update URLs to reflect current page state
  • Sitemap Generation: Include paginated URLs in your sitemap
  • Fallback Pagination: Provide traditional pagination as a fallback

Infinite Scroll Learning Outcomes

By completing the infinite scroll section, you will have gained hands-on experience with:

  • • React hooks for state management and side effects
  • • Event handling and cleanup in React applications
  • • API integration with proper error handling
  • • Performance optimization techniques (debouncing)
  • • Responsive design patterns with Tailwind CSS
  • • TypeScript integration in React applications
  • • Component composition and reusability
  • • Modern Next.js development patterns

Part 2: Mastering React Window Virtualization

Introduction

Modern web applications often need to display large datasets - think social media feeds, data tables, or file explorers with thousands of items. Rendering all these items at once creates a performance nightmare: slow initial loads, janky scrolling, and memory bloat that can crash browsers.

Virtualization solves this problem by rendering only the items visible in the viewport, plus a small buffer. This technique transforms applications that would otherwise be unusable with large datasets into smooth, responsive experiences that scale to millions of items.

The Performance Problem

Without virtualization, rendering a list of 10,000 items creates 10,000 DOM nodes immediately. Each node consumes memory and requires layout calculations, even if only 20 items are visible on screen. The browser struggles with this overhead, leading to:

Without Virtualization

10,000 items = 10,000 DOM nodes
• High memory usage
• Slow initial render
• Janky scrolling
• Browser freezing

With Virtualization

10,000 items = ~20 DOM nodes
• Constant memory usage
• Fast initial render
• Smooth scrolling
• Scales infinitely

Understanding React Window

React Window is a lightweight virtualization library that provides components for efficiently rendering large lists and grids. Created by Brian Vaughn (React core team), it offers a simpler API than its predecessor react-virtualized while maintaining excellent performance.

The library works by calculating which items are visible based on the scroll position and container dimensions, then rendering only those items plus a small buffer for smooth scrolling.

import { List } from "react-window";
import { type RowComponentProps } from "react-window";

// Basic virtualized list
<List
  rowComponent={RowComponent}
  rowCount={10000}    // Total number of items
  rowHeight={25}      // Height of each item
  rowProps={{ items }}
/>

Four Virtualization Patterns

Our tutorial demonstrates four essential virtualization patterns, each solving different use cases you'll encounter in real applications.

1. Fixed Height Lists

The simplest and most performant approach. When all items have the same height, React Window can calculate positions using simple math without measuring individual items.

// Perfect for simple lists like user names, tags, or menu items
function RowComponent({ index, names, style }: RowComponentProps<{ names: string[] }>) {
  return (
    <div className="flex items-center justify-between" style={style}>
      {names[index]}
      <div className="text-slate-500 text-xs">{`${index + 1} of ${names.length}`}</div>
    </div>
  );
}

<List
  rowComponent={RowComponent}
  rowCount={names.length}
  rowHeight={25}  // All items exactly 25px tall
  rowProps={{ names }}
/>

2. Variable Height Lists

When items have different but predictable heights, you can provide a function that returns the height for each item. This works well for hierarchical data or mixed content types.

// Different heights for different item types
function VariableRowComponent({ index, items, style }: RowComponentProps<{ items: Item[] }>) {
  const item = items[index];
  return (
    <div className="flex items-center justify-between px-2" style={style}>
      {item.type === "state" ? (
        <div className="font-semibold text-blue-600">{item.state}</div>
      ) : (
        <div className="text-sm">
          <span className="font-medium">{item.city}</span>
          <span className="text-gray-500 ml-2">{item.zip}</span>
        </div>
      )}
    </div>
  );
}

function rowHeight(index: number, { items }: { items: Item[] }) {
  return items[index].type === "state" ? 30 : 25;  // States taller than cities
}

<List
  rowComponent={VariableRowComponent}
  rowCount={items.length}
  rowHeight={rowHeight}
  rowProps={{ items }}
/>

3. Dynamic Height Lists

The most complex but flexible approach. Heights are measured at runtime and can change dynamically. Essential for content like expandable cards, comments with varying text length, or rich media items.

// Heights measured and cached automatically using hooks
const { items, toggleExpanded } = useListState(initialItems);

function DynamicRowComponent({ index, items, style }: RowComponentProps<{ items: DynamicItem[] }>) {
  const item = items[index];
  return (
    <DynamicRowComponent
      key={item.id}
      item={item}
      onToggle={() => toggleExpanded(item.id)}
      style={style}
    />
  );
}

function rowHeight(index: number, { items }: { items: DynamicItem[] }) {
  const item = items[index];
  return item.expanded ? 120 : 60;  // Expanded items are taller
}

<List
  rowComponent={DynamicRowComponent}
  rowCount={items.length}
  rowHeight={rowHeight}
  rowProps={{ items }}
/>

4. Grid Virtualization

Two-dimensional virtualization for tabular data. Virtualizes both rows and columns, making it possible to display massive spreadsheets or data tables with millions of cells.

// Virtualized data grid
function CellComponent({ contacts, columnIndex, rowIndex, style }: CellComponentProps<{ contacts: Contact[] }>) {
  const contact = contacts[rowIndex];
  const content = contact[indexToColumn(columnIndex)];
  return (
    <div className="truncate border-r border-b border-gray-200 px-2 py-1 text-sm" style={style}>
      {content}
    </div>
  );
}

<Grid
  cellComponent={CellComponent}
  cellProps={{ contacts }}
  columnCount={COLUMNS.length}
  columnWidth={columnWidth}
  rowCount={contacts.length}
  rowHeight={25}
/>

Advanced Implementation Techniques

Scroll-to-Item Navigation

Programmatically scroll to specific items using refs and the `scrollToItem` method. Essential for search functionality, bookmarks, or navigation controls.

const listRef = useListRef(null);

const scrollToRow = () => {
  const index = parseInt(scrollTarget, 10);
  if (!isNaN(index) && index >= 0 && index < items.length) {
    const list = listRef.current;
    list?.scrollToRow({
      align: "auto",
      behavior: "auto",
      index: index,
    });
  }
};

// Jump to specific items
<button onClick={scrollToRow}>
  Scroll to Row
</button>

<List listRef={listRef} rowComponent={RowComponent} rowCount={items.length} rowHeight={25} rowProps={{ items }} />

Dynamic Content with Auto-Sizing

For content that changes size dynamically, use ResizeObserver to detect size changes and update the virtualized list accordingly.

function DynamicRowComponent({ item, onToggle, style }: { item: DynamicItem; onToggle: () => void; style: React.CSSProperties }) {
  return (
    <div className="border-b border-gray-200 p-4" style={style}>
      <div className="flex items-center justify-between">
        <div>
          <h3 className="font-medium">{item.title}</h3>
          <p className="text-sm text-gray-500">{item.subtitle}</p>
        </div>
        <button onClick={onToggle} className="text-blue-500 hover:text-blue-700">
          {item.expanded ? "Collapse" : "Expand"}
        </button>
      </div>
      {item.expanded && (
        <div className="mt-3 p-3 bg-gray-50 rounded">
          <p className="text-sm">{item.description}</p>
        </div>
      )}
    </div>
  );
}

TypeScript Integration

React Window provides excellent TypeScript support with proper type definitions for all components and their props.

interface RowComponentProps<T> {
  index: number;
  style: React.CSSProperties;
} & T;

interface Contact {
  job_title: string;
  first_name: string;
  last_name: string;
  email: string;
  gender: string;
  address: string;
  city: string;
  state: string;
  zip: string;
  timezone: string;
}

function RowComponent({ index, contacts, style }: RowComponentProps<{ contacts: Contact[] }>) {
  const contact = contacts[index];
  
  return (
    <div style={style} className="px-4 py-2 border-b">
      <div className="font-semibold">{contact.first_name} {contact.last_name}</div>
      <div className="text-sm text-gray-600">{contact.email}</div>
    </div>
  );
}

Performance Benefits

Memory Efficiency

Constant memory usage regardless of dataset size. Only visible items consume DOM memory.

Rendering Speed

Initial render time stays constant even with millions of items. No more loading spinners.

Smooth Scrolling

Buttery smooth 60fps scrolling through any amount of data. Perfect user experience.

Infinite Scalability

Handle datasets of any size without performance degradation. Scale to millions of items.

Getting Started with Virtualization

Ready to implement virtualization in your React application? Follow these steps to get the tutorial project running locally:

  1. 1
    Clone the repository:
    git clone https://github.com/audoir/virtualization-tutorial.git
  2. 2
    Install dependencies:
    npm install
  3. 3
    Start the development server:
    npm run dev
  4. 4
    Explore the examples:
    http://localhost:3000 - Interactive tutorial with all four examples

Virtualization Learning Outcomes

By completing the virtualization section, you will have gained practical experience with:

  • • Understanding when and why to use virtualization
  • • Implementing fixed height lists for maximum performance
  • • Handling variable height content with predictable sizing
  • • Managing dynamic height content with automatic measurement
  • • Building virtualized grids for tabular data
  • • Adding scroll-to-item navigation functionality
  • • Integrating TypeScript for type-safe virtualization
  • • Optimizing performance for large datasets

Real-World Applications

Virtualization is essential for many types of applications:

  • Social Media Feeds - Infinite scroll through thousands of posts
  • Data Tables - Spreadsheet-like interfaces with massive datasets
  • File Explorers - Browse directories with thousands of files
  • Chat Applications - Message history with years of conversations
  • E-commerce - Product catalogs with extensive inventories
  • Admin Dashboards - User lists, transaction logs, analytics data

Conclusion

Infinite scroll and virtualization are two powerful, complementary patterns for handling large datasets in React applications. Infinite scroll provides a seamless content discovery experience by automatically loading more data as users scroll, while virtualization ensures that even massive datasets render instantly by only mounting visible DOM nodes.

Together, these techniques form the foundation for building scalable, user-friendly applications — from social media feeds and product catalogs to data-heavy dashboards and file explorers. The patterns demonstrated in this tutorial can be adapted to virtually any use case and requirement.

About the Author

Wayne Cheng is the founder and AI app developer at Audoir, LLC. Prior to founding Audoir, he worked as a hardware design engineer for Silicon Valley startups and an audio engineer for creative organizations. He holds an MSEE from UC Davis and a Music Technology degree from Foothill College.

Further Exploration

To continue exploring these implementations, check out the infinite scroll repository and the virtualization repository . Experiment with different optimization techniques, search functionality, filtering, and custom item renderers to master these powerful patterns.

For more web development tutorials and AI-powered tools, visit Audoir .