Search code examples
reactjsinfinite-scroll

InfiniteScroll keeps fetching data until Maximum update depth exceeded


In my React project I'm using react-infinite-scroller to fetch data from an API when a user scrolls to the bottom of a page. The first fetch call works as expected when the page loads, but as soon as I scroll down 50+ API calls are made and I get the Maximum update depth exceeded error. Any ideas as to what I may be doing wrong?

response.data.next is null when page=2, but from what I can see hasMore is always true.

{"count":6,"next":null,...}

Tried using useRef instead of useState for hasMore, but results are the same.

I'm following a basic example that looks like this:

import React, { useState, useEffect } from 'react';
import InfiniteScroll from 'react-infinite-scroller';
import Box from '@mui/material/Box';
import axiosInstance from '../axios';

const Test = () => {
  const [data, setData] = useState([]);
  const [hasMore, setHasMore] = useState(true);
  const [page, setPage] = useState(1);

  const fetchData = async (pageNumber) => {
    try {
      const response = await axiosInstance.get("/search/?page=" + (page))
      const newData = response.data.results;
      setData(prevData => [...prevData, ...newData]);
      setHasMore(response.data.next !== null);
    } catch (error) {
      console.error('Error fetching data:', error);
    }
  };

  useEffect(() => {
    fetchData(page);
  }, [page]);

  const loadMore = () => {
    setPage(prevPage => prevPage + 1);
  };

  return (
    <>
    <Box sx={{height: '800px'}}>

    </Box>
    <InfiniteScroll
      pageStart={0}
      loadMore={loadMore}
      hasMore={hasMore}
      loader={<div key={0}>Loading...</div>}
    >
      {data.map(item => (
        <div key={item.id}>
          {/* Render your data here */}
        </div>
      ))}
    </InfiniteScroll>
    </>
  );
};

export default Test;

Solution

  • The problem is in the way you are handling the page number. Infinity Scroll starts by calling the loadMore, as you are changing the state by setting the page number, react triggers a rerender, when it gets rerendered, if Infinity Scroll has no elements, it calls the loadMore again, as Infinity Scroll will get elements only after the API call, it will keep calling loadMore in every rerender, and as the loadMore trigger a rerender it causes an infinite rerender loop.

    There are probably more ways to solve it, but handling the page number with a Ref instead of State fixed it:

    import React, { useState } from 'react';
    import InfiniteScroll from 'react-infinite-scroller';
    import Box from '@mui/material/Box';
    import axiosInstance from '../axios';
    
    const Test = () => {
      const [data, setData] = useState([]);
      const [hasMore, setHasMore] = useState(true);
      const page = useRef(1);
    
      const fetchData = async (pageNumber) => {
        try {
          const response = await axiosInstance.get("/search/?page=" + pageNumber);
          const newData = response.data.results;
          setData(prevData => [...prevData, ...newData]);
          setHasMore(response.data.next !== null);
        } catch (error) {
          console.error('Error fetching data:', error);
        }
      };
    
      const loadMore = () => {
        fetchData(page.current++);
      };
    
      return (
        <>
        <Box sx={{height: '800px'}}>
    
        </Box>
        <InfiniteScroll
          pageStart={0}
          loadMore={loadMore}
          hasMore={hasMore}
          loader={<div key={0}>Loading...</div>}
        >
          {data.map(item => (
            <div key={item.id}>
              {/* Render your data here */}
            </div>
          ))}
        </InfiniteScroll>
        </>
      );
    };
    
    export default Test;