Giter Site home page Giter Site logo

onderonur / react-infinite-scroll-hook Goto Github PK

View Code? Open in Web Editor NEW
421.0 4.0 36.0 3.83 MB

React hook for creating infinite scroll components.

Home Page: https://onderonur.github.io/react-infinite-scroll-hook/

License: MIT License

TypeScript 89.49% HTML 2.01% JavaScript 8.19% Shell 0.31%
react reactjs react-hooks infinite-scroll infinite-scrolling hooks fetch fetching

react-infinite-scroll-hook's Introduction

react-infinite-scroll-hook

Build status License Version

All Contributors

This is a hook to create infinite scroll components!
Live demo is here.

Basically, we need to set a sentry component to trigger infinite loading. When sentry becomes visible on the screen or it comes near to be visible (based on our config of course), it triggers infinite loading (by calling onLoadMore callback) all with the help of IntersectionObserver.

sentry should be some component which will not be unmounted as long as we want to keep the infinite scrolling observer active. For example, we can use a "loading" indicator as our sentry. The trick is, because that we want to keep the infinite scrolling observer active as long as there is a next page, we need to keep this "loading" component mounted even if we don't have a loading flag as true. This will also keep our layout more consistent and prevent flickering etc.

We don't need to use a "loading" component as the sentry and we can keep them separate too. It can be anything like some empty div or last item of your list etc. We just need to place it based on our scrolling direction. To bottom if we want to trigger loading when we scroll to bottom, to top if we want to trigger it when we scroll to top like a chat message box etc. Same approach can be used with horizontal scrolling too.

Please check below to find examples with different layouts and libraries.

Note: This package uses IntersectionObserver under the hood. You might want to check the browser compatibility from here and if you want to support older browsers, you might need to use a polyfill.

Before v4, useInfiniteScroll hook would basically check the DOM with an interval and look at the distance between the bottom of your "infinite" component and the bottom of the window. This was a simple solution. But it had its difficulties. It was not so easy to change the layout of your "infinite" component (like creating a chat message box with inverted scrolling etc). It was a requirement to modify the package based on each different use case.

And also, checking the DOM with an interval by using setInterval wasn't a sophisticated solution. It was enough, but it had it's limits. With v4, we migrated to use IntersectionObserver and created a much more flexible API to support different design. Basically, now we have a little bit more inversion of control.

If you want to use the older version which is using setInterval, you can find it here.

Installation

npm install react-infinite-scroll-hook

Simple Example

import useInfiniteScroll from 'react-infinite-scroll-hook';

function SimpleInfiniteList() {
  const { loading, items, hasNextPage, error, loadMore } = useLoadItems();

  const [sentryRef] = useInfiniteScroll({
    loading,
    hasNextPage,
    onLoadMore: loadMore,
    // When there is an error, we stop infinite loading.
    // It can be reactivated by setting "error" state as undefined.
    disabled: !!error,
    // `rootMargin` is passed to `IntersectionObserver`.
    // We can use it to trigger 'onLoadMore' when the sentry comes near to become
    // visible, instead of becoming fully visible on the screen.
    rootMargin: '0px 0px 400px 0px',
  });

  return (
    <List>
      {items.map((item) => (
        <ListItem key={item.key}>{item.value}</ListItem>
      ))}
      {/* 
          As long as we have a "next page", we show "Loading" right under the list.
          When it becomes visible on the screen, or it comes near, it triggers 'onLoadMore'.
          This is our "sentry".
          We can also use another "sentry" which is separated from the "Loading" component like:
            <div ref={sentryRef} />
            {loading && <ListItem>Loading...</ListItem>}
          and leave "Loading" without this ref.
      */}
      {(loading || hasNextPage) && (
        <ListItem ref={sentryRef}>
          <Loading />
        </ListItem>
      )}
    </List>
  );
}

Or if we have a scrollable container and we want to use it as our "list container" instead of document, we just need to use rootRef like:

function InfiniteListWithVerticalScroll() {
  const { loading, items, hasNextPage, error, loadMore } = useLoadItems();

  const [sentryRef, { rootRef }] = useInfiniteScroll({
    loading,
    hasNextPage,
    onLoadMore: loadMore,
    disabled: !!error,
    rootMargin: '0px 0px 400px 0px',
  });

  return (
    <ListContainer
      // This where we set our scrollable root component.
      ref={rootRef}
    >
      <List>
        {items.map((item) => (
          <ListItem key={item.key}>{item.value}</ListItem>
        ))}
        {(loading || hasNextPage) && (
          <ListItem ref={sentryRef}>
            <Loading />
          </ListItem>
        )}
      </List>
    </ListContainer>
  );
}

Other Examples

You can find different layout examples here. Live demo contains all of these cases.

Also, we have more realistinc examples with swr and Apollo GraphQL too.

Arguments

Name Description Type Optional Default Value
loading Some sort of "is fetching" info of the request. boolean
hasNextPage If the list has more items to load. boolean
onLoadMore The callback function to execute when the 'onLoadMore' is triggered. VoidFunction
rootMargin We pass this to 'IntersectionObserver'. We can use it to configure when to trigger 'onLoadMore'. string
disabled Flag to stop infinite scrolling. Can be used in case of an error etc too. boolean
delayInMs How long it should wait before triggering 'onLoadMore' (in milliseconds). number 100

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Eugene Fidelin

💻

Evan Cater

📖

Romain

💡

This project follows the all-contributors specification. Contributions of any kind welcome!

react-infinite-scroll-hook's People

Contributors

dependabot[bot] avatar evanc123 avatar groomain avatar onderonur avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

react-infinite-scroll-hook's Issues

It is not working wiht React 18

I have upgraded my project from react 17 to react 18.
also upgraded the react-infinite-scroll-hook 4.01 to 4.04. but it is not working.

Couldnt use it with standard div

Here's my code below.
It doesnt trigger "nextPage" function, while I scroll down (at the bottom of page).

whats wrong?


  const [infiniteRef] = useInfiniteScroll({
    loading,
    hasNextPage,
    onLoadMore: nextPage,
    disabled: !!error,
    rootMargin: '0px 0px 400px 0px',
  })

        <div className="my-content" ref={infiniteRef}>
          <div className="row" >
            <div className="col-sm-12">
              <div className="row">
                {product_list.data.map((item, i) => (
                  <div className="col-md-3 col-sm-6 col-6">
                    <ProductCard {...item} />
                  </div>
                ))}
              </div>
            </div>
        </div>

Load more not triggered if viewport below sentry

Hi,

Thanks for this project - it's very good and has been a big help!

I'm using it on a page with a big footer (below the infinite list) and so if you scroll fast you can end up below the sentry and load more is not triggered. You have to scroll back up and then down again.

You can recreate it on your demo (https://onderonur.github.io/react-infinite-scroll-hook/) by decreasing the vertical height of the browser window and then scrolling quickly down so that only the footer is visible.

It may be the intended way for the hook to work but I wondered if perhaps the logic for loading more could be if the sentry is intersecting or if it's below the current viewport?

Many thanks!

Error when importing

Hello, could you help me out why i cant use this library ? i just import it to my file but it seems doesn't work well.
The error says :
Line 16:5: 'hasNextPage' is not defined no-undef
Line 17:17: 'loadMore' is not defined no-undef
Line 18:17: 'error' is not defined no-undef
Line 67:32: 'hasNextPage' is not defined no-undef

Thank you.

nextjs giving me a headache

I use to be able to use this awesome hook in my nextjs projects but now i am getting weird errors...

Unhandled Runtime Error
TypeError: Failed to execute 'observe' on 'IntersectionObserver': parameter 1 is not of type 'Element'.

Call Stack
eval
node_modules/react-intersection-observer-hook/dist/react-intersection-observer-hook.esm.js (48:0)
eval
node_modules/react-intersection-observer-hook/dist/react-intersection-observer-hook.esm.js (54:0)
eval
node_modules/react-intersection-observer-hook/dist/react-intersection-observer-hook.esm.js (58:0)
M
node_modules/preact/dist/preact.module.js (1:7563)
w
node_modules/preact/dist/preact.module.js (1:2570)
j
node_modules/preact/dist/preact.module.js (1:5833)
w
node_modules/preact/dist/preact.module.js (1:2129)
j
node_modules/preact/dist/preact.module.js (1:5833)
eval
node_modules/preact/dist/preact.module.js (1:1476)
Array.some
<anonymous>
g
node_modules/preact/dist/preact.module.js (1:1382)
eval
node_modules/@prefresh/core/src/runtime/debounceRendering.js (13:0)

Waht does that even mean and how would i start to debug this ?

thanks!

Preferred method for handling errors?

Hello!

Thank you for your work on this library. Doing a bit of due diligence before bringing it into a project.

Noticed a weird case involving error handling that causes an infinite loop.

To reproduce:

async function handleLoadMore() {
  setLoading(true)

  try {
    // API fetch typically here, but forcing an error
    throw 'Uh oh'
  } catch (error) {
    setError(true)
  }
}

const infiniteRef = useInfiniteScroll({
  loading,
  hasNextPage,
  onLoadMore: handleLoadMore,
  scrollContainer,
});

If the function onLoadMore encounters an error, it will continue getting called and enter an infinite loop.

After some trial and error, it seems like turning the loading off or setting hasNextPage to false when an error is encountered prevents this:

async function handleLoadMore() {
  setLoading(true)

  try {
    // API fetch typically here, but forcing an error
    throw 'Uh oh'
  } catch (error) {
    setError(true)
    setHasNextPage(false)
  }
}

Curious if this is the preferred way to handle errors?

Wondering if the the library should handle this internally, so that if an error is raised this condition can no longer be met:

https://github.com/onderonur/react-infinite-scroll-hook/blob/master/src/useInfiniteScroll.ts#L127

support for server components?

I am trying to use this package in nextjs app directory. But it fails because of calling client components from server files. Since the useLoadItems use useState it must be declared as client components. So it throws error when calling from page file.

Attempted to call useLoadItems() from the server but useLoadItems is on the client. 
It's not possible to invoke a client function from the server, it can only be rendered as a Component or 
passed to props of a Client Component.

Thanks.

consider exporting IntersectionObserverHookRefCallback

In order to keep results list pure, I use the sentryRef as a prop in TS. However, I can't define the type for this prop unless I install react-intersection-observer-hook and import IntersectionObserverHookRefCallback from there. Please consider exporting this in your library.

How to force a reload of the already rendered list ?

I have a search term Input, so the infinite scroll is affected by that: when I change the criteria for that Input, I need all items to be cleared and re-fetch with the new criteria. How can I achieve that?

How to import on ESM project

I'm migrating my remix app to be all ESM, my code was like this

import useInfiniteScroll from 'react-infinite-scroll-hook';

But now it produces error

TypeError: __vite_ssr_import_5__.default is not a function or its return value is not iterable

Stack pointing to my code:

Captura de pantalla 2024-04-04 a la(s) 12 43 50

I've tried

import { default as useInfiniteScroll } from 'react-infinite-scroll-hook';
import * as useInfiniteScroll from 'react-infinite-scroll-hook';

No luck!

MIT license

If it's ok with you, can you add an MIT license to this repo? Great code, easy to follow, and well written. Love it.

Doesn't work

I'm trying to use your library, but doesn't work

Could you help me, please?

Infinite loop with Reversed Vertical list

Hi,

First of all thank you so much for the amazing package,
I'm trying to implement it with react-query for a chat application.

Basically, we need to first load most recent messages and when user scrolls to the top of the container, we load old messages.
Just like whatsapp.

I got the load on scroll working and everything is fine.

Just having one small issue:
What happens is on the Initial data load by react-query the container's scroll position is at the top.
And our sentryRef element is also on the top, that triggers loadMore and after loading more still the scroll position stays at top and it just keeps loading more and more, until all the data is loaded.

could you please provide a solution to this problem 😢.

Thank you.

Here's the code:

const ChatBox = ({ thread }: ChatBoxProps) => {
  const { axios } = useAxios();

  const fetchMessages = async ({ pageParam = 0 }) => {
    const response = await axios.get(
      `/api/v1/message-threads/${thread.id}/messages`,
      {
        params: {
          page_size: 10,
          pagination_mode: "cursor",
          cursor: pageParam,
          sort_field: "created_at",
          sort_direction: "desc",
        },
      }
    );

    return response.data;
  };

  const {
    isSuccess,
    data,
    isFetching,
    isFetchingNextPage,
    fetchNextPage,
    hasNextPage,
    isError,
  } = useInfiniteQuery<MessagesResponse>({
    queryKey: ["messages"],
    queryFn: fetchMessages,
    select: (data) => {
      return {
        pages: [...data.pages].reverse(),
        pageParams: [...data.pageParams].reverse(),
      };
    },
    getNextPageParam: (lastPage, pages) => lastPage.next_cursor,
  });

  const [sentryRef, { rootRef }] = useInfiniteScroll({
    loading: isFetching,
    hasNextPage: !!hasNextPage,
    onLoadMore: () => fetchNextPage(),
    disabled: isError,
    rootMargin: "0px 0px 0px 0px",
  });

  return (
    <div className="flex flex-grow flex-col divide-y border-b bg-white">
      <ChatHeader thread={thread} />

      {/* Chat Messages */}
      <div
        ref={rootRef}
        className="bg-accent h-0 flex-auto flex-col space-y-3 overflow-auto px-4"
      >
        {isSuccess && (isFetchingNextPage || hasNextPage) && (
          <div ref={sentryRef} className="py-3 text-center text-slate-500">
            Loading old chats...
          </div>
        )}

        {data?.pages.map((group, i) => (
          <Fragment key={i}>
            {[...group.data].reverse().map((message) => (
              <MessageBubble
                key={message.id}
                message={message}
                direction={
                  thread.contact_phone_number == message.from
                    ? "inbound"
                    : "outbound"
                }
              />
            ))}
          </Fragment>
        ))}
      </div>

      <MessageSender />
    </div>
  );
};

Should we map the threshold to a ratio instead of a px?

Hello everyone, could not find a template for this one.

I was wondering if the threshold could be a ratio of the container instead of a pixel distance, this representation is common in other infinite-list components and what not and seems to me that relates better with the mindset of "when should we load more" since we live on the world of responsiveness.

I know this might be a breaking change, so we there's should be some caution. Either way, here's an example of what I mean?

 const infiniteRef = useInfiniteScroll<HTMLDivElement>({
   ...
    threshold:  0.5 // trigger loadMore in the middle of the scroll parent
  })

Cheers for the awesomew hook, hope I can help with this one!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.