skip to Main Content

I am struggling to find a pattern that works for an infinite scroll component I am working on for reference I am using the react-intersection-observer hook. The problem is that my fetchData function is being called twice when the inView state is true. I have tried quite a few solutions and none of them seem to work. The flow seems to be:

  1. Page loads inView is false.
  2. User scrolls inView becomes true.
  3. fetchData is asynchronously called and useEffect ends.
  4. res is finally returned and setQueryData finally updates the state.
  5. This triggers the useEffect to execute again but since the data has just been set, it hasn’t been rendered yet and pushed the inView linked component down out of the way.
  6. Since inView is still true another fetchData is executed.

It seems like I need to make inView false immediately after the useEffect is run but not sure if this possible or correct, any advice would be greatly appreciated!

type LoadMoreState = {
  data: QueryDatabaseResponse[];
  nextCursor: string | undefined;
};

export function LoadMore({ cursor }: { cursor: string }) {
  const { ref, inView } = useInView();
  const [queryData, setQueryData] = useState<LoadMoreState>({
    data: [],
    nextCursor: cursor,
  });

  useEffect(() => {
    const fetchData = async () => {
      if (inView) {
        try {
          const res = await getGoodFirstIssues({
            page_size: 10,
            start_cursor: queryData.nextCursor,
          });
          setQueryData((prevData) => ({
            data: prevData.data.concat(res),
            nextCursor: res.next_cursor || undefined,
          }));
        } catch (error) {
          console.log("There was an error fetching the data", error);
        }
      }
    };
    fetchData();
  }, [inView, queryData]);

2

Answers


  1. removing queryData from the dependency array, you prevent the useEffect from running when queryData changes. This should help avoid the double-fetching issue you were experiencing. Additionally, make sure that you’re rendering the LoadMore component properly in your main component tree.

     useEffect(() => {
       ...
      }, [inView]);
    

    If the structure of your component requires using queryData within the useEffect, consider using the functional update form of setQueryData to avoid dependency-related issues:

    setQueryData((prevData) => {
      if (inView) {
        // Your logic here
      }
      return prevData; // Return the unchanged state if inView is false
    });
    
    Login or Signup to reply.
  2. The problem is that you need queryData inside the useEffect, because of the nextCursor, but it also triggers the useEffect, while inView is still true.

    Store the next_cursor in a ref (nextCursorRef), and remove it from the dependencies of the useEffect:

    export function LoadMore({ cursor }: { cursor: string }) {
      const nextCursorRef = useRef<string | undefined>();
      const { ref, inView } = useInView();
      const [queryData, setQueryData] = useState<QueryDatabaseResponse[]>([]);
    
      useEffect(() => {
        const fetchData = async () => {
          if (inView) {
            try {
              const res = await getGoodFirstIssues({
                page_size: 10,
                start_cursor: nextCursorRef.current,
              });
              
              nextCursorRef.current = res.next_cursor || undefined;
              setQueryData((prevData) => prevData.concat(res));
            } catch (error) {
              console.log("There was an error fetching the data", error);
            }
          }
        };
        fetchData();
      }, [inView]);
    

    I would also refactor this a bit more, to prevent defining and calling fetchData if inView is false:

    export function LoadMore({ cursor }: { cursor: string }) {
      const nextCursorRef = useRef<string | undefined>();
      const { ref, inView } = useInView();
      const [queryData, setQueryData] = useState<QueryDatabaseResponse[]>([]);
    
      useEffect(() => {
        if (!inView) return undefined; // if not in view no need to define and call fetchData
    
        const fetchData = async () => {
          try {
            const res = await getGoodFirstIssues({
              page_size: 10,
              start_cursor: nextCursorRef.current,
            });
    
            nextCursorRef.current = res.next_cursor || undefined;
            setQueryData((prevData) => prevData.concat(res));
          } catch (error) {
            console.log("There was an error fetching the data", error);
          }
        };
        fetchData();
      }, [inView]);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search