reactjsreact-nativereact-hooksinfinite-scrolljquery-waypoints

React Infinite Loading hook, previous trigger


Im trying to make a hook similar to Waypoint.

I simply want to load items and then when the waypoint is out of screen, allow it to load more items if the waypoint is reached.

I can't seem to figure out the logic to have this work properly.

Currently it see the observer state that its on the screen. then it fetches data rapidly.

I think this is because the hook starts at false everytime. Im not sure how to make it true so the data can load. Followed by the opposite when its reached again.

Any ideas.

Here's the hook:

import { useEffect, useState, useRef, RefObject } from 'react';

export default function useOnScreen(ref: RefObject<HTMLElement>) {
  const observerRef = useRef<IntersectionObserver | null>(null);
  const [isOnScreen, setIsOnScreen] = useState(false);

  useEffect(() => {
    observerRef.current = new IntersectionObserver(([entry]) => {
      if (isOnScreen !== entry.isIntersecting) {
        setIsOnScreen(entry.isIntersecting);
      }
    });
  }, []);

  useEffect(() => {
    observerRef.current.observe(ref.current);

    return () => {
      observerRef.current.disconnect();
    };
  }, [ref]);

  return isOnScreen;
}

Here's the use of it:

import React, { useRef } from 'react';
import { WithT } from 'i18next';

import useOnScreen from 'utils/useOnScreen';

interface IInboxListProps extends WithT {
  messages: any;
  fetchData: () => void;
  searchTerm: string;
  chatID: string | null;
}

const InboxList: React.FC<IInboxListProps> = ({ messages, fetchData, searchTerm, chatID}) => {
  const elementRef = useRef(null);
  const isOnScreen = useOnScreen(elementRef);

  if (isOnScreen) {
    fetchData();
  }

  
  const renderItem = () => {
    return (
      <div className='item unread' key={chatID}>
      Item
      </div>
    );
  };

  const renderMsgList = ({ messages }) => {
    return (
      <>
        {messages.map(() => {
          return renderItem();
        })}
      </>
    );
  };

  let messagesCopy = [...messages];

  //filter results
  if (searchTerm !== '') {
    messagesCopy = messages.filter(msg => msg.user.toLocaleLowerCase().startsWith(searchTerm.toLocaleLowerCase()));
  }

  return (
    <div className='conversations'>
      {renderMsgList({ messages: messagesCopy })}
      <div className='item' ref={elementRef} style={{ bottom: '10%', position: 'relative',backgroundColor:"blue",width:"5px",height:"5px" }} />
    </div>
  );
};

export default InboxList;

Solution

  • Let's inspect this piece of code

      const [isOnScreen, setIsOnScreen] = useState(false);
    
      useEffect(() => {
        observerRef.current = new IntersectionObserver(([entry]) => {
          if (isOnScreen !== entry.isIntersecting) {
            setIsOnScreen(entry.isIntersecting);
          }
        });
      }, []);
    

    We have the following meanings:

    and

    When using a xor (!==) you specify that it:

    What you want to do is to get more items each time the element intersects

    export default function useOnScreen(ref: RefObject<HTMLElement>, onIntersect: function) {
      const observerRef = useRef<IntersectionObserver | null>(null);
      const [isOnScreen, setIsOnScreen] = useState(false);
    
      useEffect(() => {
        observerRef.current = new IntersectionObserver(([entry]) => {
          setIsOnScreen(entry.isIntersecting);
        });
      }, []);
      
      useEffect(()=?{
        if(isOnScreen){
           onIntersect();
        }
      },[isOnScreen,onIntersect])
    
      ...
    }
    

    and then use it like:

      const refetch= useCallback(()=>{
        fetchData();
      },[fetchData]);
    
      const isOnScreen = useOnScreen(elementRef, refetch);
    

    or simply:

      const isOnScreen = useOnScreen(elementRef, fetchData);
    

    If fetchData changes reference for some reason, you might want to use the following instead:

      const refetch= useRef(fetchData);
    
      const isOnScreen = useOnScreen(elementRef, refetch);
    

    Remember that useOnScreen has to call it like onIntersect.current()