DEV Community

Cover image for Easy React Infinite Scroll👌
Apestein
Apestein

Posted on

4 1 1

Easy React Infinite Scroll👌

Introduction

I had a hard time implementing a react infinite scroll feature so I decided to make an npm package to make it super simple. If you have ever tried to implement a react infinite scroll feature you might have seen react-infinite-scroll-component and react-finite-scroller. The problem with these packages are:

  1. They are large, which makes them hard to customize.
  2. Written as class component, also hard to customize.
  3. Uses the event listener on the scroll event which is not performant. Mine uses the modern intersection observer API.

Better React Infinite Scroll

NPM
Demo
Source Code

Install or just copy and paste...

import React, { useEffect, useRef } from "react";

interface InfiniteScrollProps extends React.ComponentPropsWithRef<"div"> {
  fetchNextPage: () => any;
  hasNextPage: boolean;
  loadingMessage: React.ReactNode;
  endingMessage: React.ReactNode;
}

export default function InfiniteScroller(props: InfiniteScrollProps) {
  const {
    fetchNextPage,
    hasNextPage,
    loadingMessage,
    endingMessage,
    children,
    ...rest
  } = props;
  const observerTarget = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0]?.isIntersecting) {
          void fetchNextPage();
        }
      },
      { threshold: 1 }
    );

    if (observerTarget.current) {
      observer.observe(observerTarget.current);
    }

    return () => {
      if (observerTarget.current) {
        observer.unobserve(observerTarget.current);
      }
    };
  }, [observerTarget]);

  return (
    <div {...rest} style={{ overflowAnchor: "none" }}>
      {children}
      <div ref={observerTarget}></div>
      {hasNextPage && loadingMessage}
      {!hasNextPage && endingMessage}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

How to use: normal scroll

import InfiniteScroller from "better-react-infinite-scroll";

return (
  <InfiniteScroller
    fetchNextPage={fetchNextPage}
    hasNextPage={hasNextPage}
    loadingMessage={<p>Loading...</p>}
    endingMessage={<p>The beginning of time...</p>}
  >
    {elements.map((el) => (
      <div key={el.id}>{el}</div>
    ))}
  </InfiniteScroller>
);
Enter fullscreen mode Exit fullscreen mode

How to use: inverse scroll

For inverse scroll, use flex-direction: column-reverse. Scoller height must be defined. Here we use tailwind flex-1 (flex: 1 1 0%) but height: 300px would also work for example.

import InfiniteScroller from "better-react-infinite-scroll";

return (
  <div className="flex h-screen flex-col">
    <InfiniteScroller
      fetchNextPage={fetchNextPage}
      hasNextPage={hasNextPage}
      loadingMessage={<p>Loading...</p>}
      endingMessage={<p>The beginning of time...</p>}
      className="flex flex-1 flex-col-reverse overflow-auto"
    >
      {elements.map((el) => (
        <div key={el.id}>{el}</div>
      ))}
    </InfiniteScroller>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

Full example with tRPC and React Query (TanStack Query)

import InfiniteScroller from "better-react-infinite-scroll";

//if using with tRPC
const { data, fetchNextPage, hasNextPage } = api.main.getAll.useInfiniteQuery(
  {
    limit: 25,
  },
  {
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  }
);

//if using with React Query (TanStack)
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
  queryKey: ["projects"],
  queryFn: fetchProjects,
  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
});

function aggregatePosts() {
  const pages = data?.pages;
  const posts = pages?.reduce((prev, current) => {
    const combinedPosts = prev.posts.concat(current.posts);
    const shallowCopy = { ...prev };
    shallowCopy.posts = combinedPosts;
    return shallowCopy;
  }).posts;
  return posts;
}

return (
  <>
    <InfiniteScroller
      fetchNextPage={fetchNextPage}
      hasNextPage={hasNextPage}
      loadingMessage={<p>Loading...</p>}
      endingMessage={<p>The beginning of time...</p>}
    >
      {aggregatePosts()?.map((post) => (
        <li key={post.id}>{post.content}</li>
      ))}
    </InfiniteScroller>
  </>
);
Enter fullscreen mode Exit fullscreen mode

tRPC docs
React Query docs

If you find this useful, please star this repo on Github. Also, follow me on Twitter for tech advise and hot takes.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

The best way to debug slow web pages cover image

The best way to debug slow web pages

Tools like Page Speed Insights and Google Lighthouse are great for providing advice for front end performance issues. But what these tools can’t do, is evaluate performance across your entire stack of distributed services and applications.

Watch video

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay