1

I'm working on a fairly simple React functional component that gets a value (tag) from the URL and requests documents from firestore correspondig to that tag (inlc. firestore query cursor to use with a load more button). The whole component consists of a function declaration and a useEffect hook that calls this function.

Basically the component works fine. But there's a catch: ESLint tells me that I need to include the function as a dependency of the useEffect hook:

React Hook useEffect has a missing dependency: 'getNextImages'. Either include it or remove the dependency array react-hooks/exhaustive-deps

If I include the function into the dependency array or remove the dependency array entirely, the component ends up in a infinite render loop, of course. I could skip the dependency array for ESLint with // eslint-disable-next-line, but that feels not right.

I tried to fix this by wrapping getNextImages into a useCallback hook without success. But maybe I've done it the wrong way. I never had to use useCallback before…

Is there anybody able to tell me a hint?

You'll find my component at https://codesandbox.io/s/great-liskov-d12l3?file=/src/App.js. Or here:

import { useEffect, useState } from "react";
import firebase from "firebase";
import { useParams } from "react-router-dom";

const GalleryTag = () => {
  //const [isPending, setIsPending] = useState(true);
  //const [galleryData, setGalleryData] = useState([]);
  //const [lastDocument, setLastDocument] = useState(null);
  //const [isLastPage, setIsLastPage] = useState(false);
  const numImagesPerPage = 20;
  const { tag } = useParams();

  const getNextImages = (lastDocument) => {
    let query;
    !lastDocument
      ? (query = firebase
          .firestore()
          .collectionGroup("images")
          .where("showinapp", "==", true)
          .where("tags", "array-contains", tag)
          .orderBy("createdate", "desc")
          .limit(numImagesPerPage)
          .get())
      : (query = firebase
          .firestore()
          .collectionGroup("images")
          .where("showinapp", "==", true)
          .where("tags", "array-contains", tag)
          .orderBy("createdate", "desc")
          .startAfter(lastDocument)
          .limit(numImagesPerPage)
          .get());
    query
      .then((data) => {
        setIsLastPage(data.empty);
        setIsPending(false);
        setLastDocument(data.docs[data.docs.length - 1]);
        setGalleryData((galleryData) => galleryData.concat(data.docs));
      })
      .catch((error) => console.error(error));
  };

  useEffect(() => {
    getNextImages();
  }, [tag]);

  return <>JSX goes here then</>;
};

export default GalleryTag;
Ralph
  • 35
  • 1
  • 8

2 Answers2

2

Try to move your function into useEffect callback:

  
  useEffect(() => {
    const getNextImages = (lastDocument) => {
      let query;
      !lastDocument
        ? (query = firebase
            .firestore()
            .collectionGroup("images")
            .where("showinapp", "==", true)
            .where("tags", "array-contains", tag)
            .orderBy("createdate", "desc")
            .limit(numImagesPerPage)
            .get())
        : (query = firebase
            .firestore()
            .collectionGroup("images")
            .where("showinapp", "==", true)
            .where("tags", "array-contains", tag)
            .orderBy("createdate", "desc")
            .startAfter(lastDocument)
            .limit(numImagesPerPage)
            .get());
      query
        .then((data) => {
          setIsLastPage(data.empty);
          setIsPending(false);
          setLastDocument(data.docs[data.docs.length - 1]);
          setGalleryData((galleryData) => galleryData.concat(data.docs));
        })
        .catch((error) => console.error(error));
    };
    getNextImages();
  }, [tag]);
Taghi Khavari
  • 6,272
  • 3
  • 15
  • 32
  • 1
    This works partially but in this case I can't access getNextImages() from onClick on the »load more« button that's rendered in JSX to get the next batch of images. – Ralph Jan 30 '21 at 19:21
  • @Ralph put all the code inside the `codesandbox` so I could edit it based on your needs – Taghi Khavari Jan 30 '21 at 19:31
  • Done that. Thank you for having a look into it, Thagi. – Ralph Jan 30 '21 at 19:35
0

Now I got the solution. It's called useCallback hook.

After playing around with useCallback and its dependency array I only had to wrap the function getNextImages() in this hook and set its dependencies (tag in this case).

Then I could set the function getNextImages as a dependency of the useEffect hook below… and voilà: ESLint is happy now (also the build process of course). The re-renders and inifinite execution loops are also prevented.

Here's the working code:

const getNextImages = useCallback(
    (lastDocument) => {
      let query;
      !lastDocument
        ? (query = firebase
            .firestore()
            .collectionGroup("images")
            .where("showinapp", "==", true)
            .where("tags", "array-contains", tag)
            .orderBy("createdate", "desc")
            .limit(numImagesPerPage)
            .get())
        : (query = firebase
            .firestore()
            .collectionGroup("images")
            .where("showinapp", "==", true)
            .where("tags", "array-contains", tag)
            .orderBy("createdate", "desc")
            .startAfter(lastDocument)
            .limit(numImagesPerPage)
            .get());
      query
        .then((data) => {
          setIsLastPage(data.empty);
          setIsPending(false);
          setLastDocument(data.docs[data.docs.length - 1]);
          setGalleryData((galleryData) => galleryData.concat(data.docs));
        })
        .catch((error) => console.error(error));
    },
    [tag]
  );

  useEffect(() => {
    getNextImages();
    setPageTitle(`Bilder zum Tag »${tag}«`);
    sendAnalytics("page_view", {
      page_title: `Bilder zum Tag »${tag}«`,
    });
  }, [getNextImages, tag]);
Ralph
  • 35
  • 1
  • 8