0
0
NextjsDebug / FixBeginner · 4 min read

How to Handle Infinite Scroll in Next.js: Fix and Best Practices

To handle infinite scroll in Next.js, use React hooks like useState and useEffect to load data in pages and detect scroll position. Fetch new data when the user nears the bottom, updating state to append items without reloading the page.
🔍

Why This Happens

Infinite scroll often breaks when the scroll event is not handled properly or when data fetching logic causes repeated or no loading of new items. A common mistake is not checking if the user reached near the bottom before fetching more data, causing too many requests or no new data loading.

javascript
import { useState, useEffect } from 'react';

export default function InfiniteScroll() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);

  useEffect(() => {
    fetch(`/api/items?page=${page}`)
      .then(res => res.json())
      .then(newItems => setItems(newItems));
  }, [page]);

  useEffect(() => {
    const handleScroll = () => {
      if (window.scrollY + window.innerHeight >= document.body.scrollHeight) {
        setPage(page + 1); // This uses stale page value
      }
    };
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  return (
    <ul>
      {items.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}
Output
The list does not load more items after the first page or loads repeatedly causing performance issues.
🔧

The Fix

Fix the infinite scroll by properly handling the scroll event and updating the page state using a functional update to avoid stale closures. Also, clean up the event listener to prevent memory leaks. This ensures new data loads only when the user scrolls near the bottom.

javascript
import { useState, useEffect } from 'react';

export default function InfiniteScroll() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    fetch(`/api/items?page=${page}`)
      .then(res => res.json())
      .then(newItems => {
        setItems(prev => [...prev, ...newItems]);
        setLoading(false);
      });
  }, [page]);

  useEffect(() => {
    function handleScroll() {
      if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100 && !loading) {
        setPage(prevPage => prevPage + 1);
      }
    }

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [loading]);

  return (
    <>
      <ul>
        {items.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
      {loading && <p>Loading more items...</p>}
    </>
  );
}
Output
The list loads more items smoothly as the user scrolls near the bottom, showing a loading message while fetching.
🛡️

Prevention

To avoid infinite scroll issues, always debounce or throttle scroll events if needed, clean up event listeners, and use functional updates for state changes inside event handlers. Also, track loading state to prevent multiple simultaneous fetches. Testing on different screen sizes helps ensure smooth user experience.

⚠️

Related Errors

Common related errors include:

  • Multiple fetches: Happens when loading state is not tracked, causing repeated API calls.
  • Memory leaks: Occur if event listeners are not removed on component unmount.
  • Stale closures: Using state variables directly inside event listeners without functional updates causes outdated values.

Key Takeaways

Use functional state updates inside scroll event handlers to avoid stale values.
Always clean up scroll event listeners in useEffect to prevent memory leaks.
Track loading state to avoid multiple simultaneous data fetches.
Fetch and append new data only when the user scrolls near the bottom.
Test infinite scroll on various devices and screen sizes for smooth UX.