react-infinite-scroll-component icon indicating copy to clipboard operation
react-infinite-scroll-component copied to clipboard

Don't loading new content, if initial content height <= screen height

Open sewaca opened this issue 2 years ago • 5 comments

Infinite scroll can't call "next" function if page (or div) has no scroll So, if user use scale 25% page (or div) will have no scroll, so it will be broken

sewaca avatar Sep 25 '23 10:09 sewaca

I found the same problem and had to create the following hook as workaround

The hook, just change the scrollable element querySelector, or add it to the hook as a parameter:

import { useCallback, useEffect, useMemo } from 'react'

function useFixMissingScroll({ hasMoreItems, fetchMoreItems }) {
  const mainElement = useMemo(() => document.querySelector('main'), [])

  const fetchCb = useCallback(() => {
    fetchMoreItems()
  }, [fetchMoreItems])

  useEffect(() => {
    const hasScroll = mainElement ? mainElement.scrollHeight > mainElement.clientHeight : false
    if (!hasScroll && hasMoreItems) {
      setTimeout(() => {
        fetchCb()
      }, 100)
    }
  }, [hasMoreItems, fetchCb, mainElement])
}

export default useFixMissingScroll

Usage:

useFixMissingScroll({
    hasMoreItems: hasMoreElements,
    fetchMoreItems: getNextPage
  })

eamador avatar Sep 25 '23 17:09 eamador

Can you explain what needs to be changed in the querySelector?

Daydh7 avatar Oct 26 '23 18:10 Daydh7

@eamador Thank you for your contribution, is your hook self-sufficient, or do you still need to use the react-infinite-scroll-component ?

Kaz- avatar Jan 08 '24 08:01 Kaz-

I found the same problem and had to create the following hook as workaround

The hook, just change the scrollable element querySelector, or add it to the hook as a parameter:

import { useCallback, useEffect, useMemo } from 'react'

function useFixMissingScroll({ hasMoreItems, fetchMoreItems }) {
  const mainElement = useMemo(() => document.querySelector('main'), [])

  const fetchCb = useCallback(() => {
    fetchMoreItems()
  }, [fetchMoreItems])

  useEffect(() => {
    const hasScroll = mainElement ? mainElement.scrollHeight > mainElement.clientHeight : false
    if (!hasScroll && hasMoreItems) {
      setTimeout(() => {
        fetchCb()
      }, 100)
    }
  }, [hasMoreItems, fetchCb, mainElement])
}

export default useFixMissingScroll

Usage:

useFixMissingScroll({
    hasMoreItems: hasMoreElements,
    fetchMoreItems: getNextPage
  })

It seems in your solution, fetchMoreItems would only be called once more. Chances are that after this fetch, still mainElement.scrollHeight <= mainElement.clientHeight and the remaining data won't be fetched.

Elonnn avatar May 03 '24 21:05 Elonnn

Here's what I did; this worked for multiple loads required to fill the screen and correctly stopped once the screen is full. (This does not handle window resizing/zoom.)

// Generate random div ID one time
const [scrollableTarget] = useState(
  () => `scrollableDiv-${String(Math.random()).substring(2)}`,
);

const lastChildrenRef = useRef<any>(null);
useEffect(() => {
  // Can only do action if there's more to fetch
  if (!hasMore) {
    return;
  }
  // Trigger change only on children change
  if (lastChildrenRef.current === children) {
    return;
  }
  lastChildrenRef.current = children;

  const containerElement = document.querySelector(`#${scrollableTarget}`);
  const innerElement = document.querySelector(
    `#${scrollableTarget} > div > div.infinite-scroll-component`,
  );
  if (!containerElement || !innerElement) return;
  const hasScroll = innerElement.scrollHeight > containerElement.clientHeight;
  if (!hasScroll) {
    setTimeout(next, 250);
  }
}, [hasMore, next, scrollableTarget, children]);

return (
  <div
    id={scrollableTarget}
    style={{
      overflowY: "scroll",
      display: "flex",
      flexDirection: "column-reverse",
      flex: 1,
    }}
  >
    <InfiniteScroll
      scrollableTarget={scrollableTarget}
      next={next}
      hasMore={hasMore}
      loader={<h4>Loading...</h4>}
      dataLength={dataLength}
      style={{ display: "flex", flexDirection: "column-reverse" }} //To put endMessage and loader to the top.
      inverse={true}
    >
      {children}
    </InfiniteScroll>
  </div>
);

benjie avatar Jul 23 '24 16:07 benjie