ankeetmaini / react-infinite-scroll-component

An awesome Infinite Scroll component in react.
https://react-infinite-scroll-component.netlify.com/
MIT License
2.85k stars 322 forks source link

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

Open sewaca opened 11 months ago

sewaca commented 11 months ago

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

eamador commented 11 months ago

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
  })
Daydh7 commented 10 months ago

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

Kaz- commented 8 months ago

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

Elonnn commented 4 months ago

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.

benjie commented 1 month ago

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>
);