Search code examples
javascriptreactjsreact-infinite-scroll-component

How to remove repeatable API results when using react-infinite-scroll and react hooks


I'm working with react-infinite-scroll-component library and randomuser.me api. I can fetch the data and the infinite scroll works just fine but the problem I have is that since the component makes a request at every scroll, I'm getting repeatable results.

I've found a workaround that removes arrays that are the same but it is not working properly with the infinite scroll package.

This is my code:

function App() {
  const [people, setPeople] = useState([]);

  const fetchPeopleImages = () => {
    axios.get(`https://randomuser.me/api/?results=30&nat=br`).then((res) => {
      const result = res.data.results;
      setPeople([...people, ...result]);
    });
    // if (people.length > 30) {
    //   const removedDuplicatedPeople = people.filter(
    //     (ele, ind) =>
    //       ind ===
    //       people.findIndex(
    //         (elem) => elem.picture.medium === ele.picture.medium,
    //       ),
    //   );
    //   setPeople([...people, removedDuplicatedPeople]);
    // }
  };

  useEffect(() => {
    fetchPeopleImages();
    // commented below because ESLINT was asking me to use useCallback 
    // inside fetchPeopleImage func. idk why
    // eslint-disable-next-line
  }, []);

  return (
    <div className="App">
      <InfiniteScroll
        dataLength={people.length}
        next={() => fetchPeopleImages()}
        hasMore={true}
        loader={<h4>Carregando...</h4>}
        endMessage={
          <p style={{ textAlign: 'center' }}>
            <b>Yay! You have seen it all</b>
          </p>
        }
      >
        {people.length > 1 &&
          people.map((people, i) => (
            <div>
              <img src={people.picture.medium} alt="Imagem de uma pessoa" />
            </div>
          ))}
      </InfiniteScroll>
    </div>
  );
}

export default App;

What is commented was the workaround I've found to remove arrays that have the same image link. Codesandbox: https://codesandbox.io/s/gracious-wildflower-opjx5?file=/src/App.js


Solution

  • Ideally don't do the filtering business specially with frequent fetches (like scroll). It is better if you maintain a state say page and pass it to your api and the api should return the data.

    The problem with the inconsistency you are facing is due to the limitations of randomuser.me api. The api has only limited images to serve, so it will mix up the names, ages, images etc and tries its best to serve unique records. Hence you will often see duplicate images. You can check by rendering the name along with image and you will see 2 same images will have different names.

    Some suggestions to solve your issue: - provide a low value to results query param say 10 - use seed option to the api - on every scroll increment the page and pass it to the api query param

    See updated demo here - it works to some extent

    Note - you are still not guaranteed to see unique images rendered. However, you can use the updated code and it will work when you use it with real api.

    updated code snippet

    function App() {
      const [people, setPeople] = useState([]);
      const [page, setPage] = useState(0);
    
      const fetchPeopleImages = () => {
        axios
          .get(`https://randomuser.me/api/?results=10&nat=br&page=${page}&seed=abc`)
          .then(res => {
            const result = res.data.results;
            setPeople([...people, ...result]);
            setPage(prev => prev + 1);
          });
        console.log("page", page);
    
      };
    
      useEffect(() => {
        fetchPeopleImages();
        // commented below because ESLINT was asking me to use useCallback inside
        // fetchPeopleImage func. idk why
        // eslint-disable-next-line
      }, []);
    
      return (
        <div className="App">
          <InfiniteScroll
            dataLength={people.length}
            next={() => fetchPeopleImages()}
            hasMore={true}
            loader={<h4>Loading.....</h4>}
            endMessage={
              <p style={{ textAlign: "center" }}>
                <b>Yay! You have seen it all</b>
              </p>
            }
          >
            {people.length > 1 &&
              people.map((people, i) => (
                <div key={i}>
                  <img src={people.picture.medium} alt="Person" />
                  <p>{people.name.first}</p>
                </div>
              ))}
          </InfiniteScroll>
        </div>
      );
    }