Vyse Clown
Vyse Clown

Reputation: 49

Error using custom hook inside a useEffect

 let { photos, isQuering, empty, error } = useFetch(brand, isOld);

  useEffect(() => {
    if (isOld) {
      const { photos: photosTest } = useFetch(brand, isOld);
      photos = photosTest;
    }
  }, [isOld]);

useFetch is a custom hook that I have and I want to bring the old photos when the isOld state is true, the code above useEffect is called normally and the photos load, but I run into the error that useFetch is not being called inside the body a function component, the following error appears "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:", that is, I am doing something very wrong that I cannot to see! If you can help me, I would appreciate it very much!

Editing because of Danko! The Hook!

import { useEffect, useState, useContext } from 'react';
import { useScrollPagination } from './flow-manager';
import { db } from '../../Firebase';
import { userContext } from '../appContext';

export default function fetch(brand, isOld) {
  const {
    userData: { uid },
  } = useContext(userContext);

  const [photos, setPhotos] = useState([]);
  const [lastDoc, setLastDoc] = useState(undefined);
  const [isQuering, setIsQuering] = useState(false);
  const [empty, setEmpty] = useState(false);
  const [error, setError] = useState();
  const [finished, setFinished] = useState(false);
  const shouldFetchMore = useScrollPagination();
  const [shouldKeepFecthing, setShouldKeepFetching] = useState(false);

  useEffect(() => {
    if (isQuering || finished) return;
    if (!lastDoc || shouldFetchMore || shouldKeepFecthing) {
      setIsQuering(true);
      let query = !isOld
        ? db
            .collection('catalog-images')
            .where('brandName', '==', brand)
            .orderBy('timestamp', 'desc')
            .endBefore(new Date().setDate(new Date().getDate() - 40))
            .limit(20)
        : db
            .collection('catalog-images')
            .where('brandName', '==', brand)
            .where('photoPeriod', '==', 'Antiga')
            .limit(20);

      if (lastDoc) query = query.startAfter(lastDoc);

      query
        .get()
        .then(snap => {
          const newPhotos = [];
          let valid = 0;
          snap.forEach(doc => {
            const { url, pricetag, timestamp } = doc.data();
            if (!uid && pricetag === 'Sim') return;
            brand && newPhotos.push({ url, timestamp });
            valid += 1;
          });
          setPhotos(oldPhotos => [...oldPhotos, ...newPhotos]);
          setShouldKeepFetching(valid < 10);
          setEmpty(snap.empty);
          setLastDoc(snap.docs[snap.docs.length - 1]);
          setFinished(snap.docs.length < 20);
          setIsQuering(false);
        })
        .catch(setError);
    }
  }, [!!lastDoc, shouldFetchMore, shouldKeepFecthing, isQuering]);
  return { photos, isQuering, empty, error, fetch };
}

Last Update: Here, where I am calling the hook:

let {
    photos,
    isQuering,
    empty,
    error,
    useFetch: refetch,
  } = useFetch(brand, isOld);

  useEffect(() => {
    if (isOld) {
      let { photos: photosTest } = refetch(brand, isOld);
      photos = photosTest;
      setIsOld(false);
    }
  }, [isOld]);

Aaaand, the hook:

import { useEffect, useState, useContext } from 'react';
import { useScrollPagination } from './flow-manager';
import { db } from '../../Firebase';
import { userContext } from '../appContext';

export default function useFetch(brand, isOld) {
  const {
    userData: { uid },
  } = useContext(userContext);

  const [photos, setPhotos] = useState([]);
  const [lastDoc, setLastDoc] = useState(undefined);
  const [isQuering, setIsQuering] = useState(false);
  const [empty, setEmpty] = useState(false);
  const [error, setError] = useState();
  const [finished, setFinished] = useState(false);
  const shouldFetchMore = useScrollPagination();
  const [shouldKeepFecthing, setShouldKeepFetching] = useState(false);

  useEffect(() => {
    if (isQuering || finished) return;
    if (!lastDoc || shouldFetchMore || shouldKeepFecthing) {
      setIsQuering(true);
      let query = !isOld
        ? db
            .collection('catalog-images')
            .where('brandName', '==', brand)
            .orderBy('timestamp', 'desc')
            .endBefore(new Date().setDate(new Date().getDate() - 40))
            .limit(20)
        : db
            .collection('catalog-images')
            .where('brandName', '==', brand)
            .where('photoPeriod', '==', 'Antiga')
            .limit(20);

      if (lastDoc) query = query.startAfter(lastDoc);

      query
        .get()
        .then(snap => {
          const newPhotos = [];
          let valid = 0;
          snap.forEach(doc => {
            const { url, pricetag, timestamp } = doc.data();
            if (!uid && pricetag === 'Sim') return;
            brand && newPhotos.push({ url, timestamp });
            valid += 1;
          });
          setPhotos(oldPhotos => [...oldPhotos, ...newPhotos]);
          setShouldKeepFetching(valid < 10);
          setEmpty(snap.empty);
          setLastDoc(snap.docs[snap.docs.length - 1]);
          setFinished(snap.docs.length < 20);
          setIsQuering(false);
        })
        .catch(setError);
    }
  }, [!!lastDoc, shouldFetchMore, shouldKeepFecthing, isQuering]);
  return { photos, isQuering, empty, error, useFetch };
}

Upvotes: 0

Views: 4230

Answers (2)

vkurchatkin
vkurchatkin

Reputation: 13570

The thing is, you can't call a hook from another hooks. Hooks are only called from component body (top-level). Your code makes no sense on a few levels:

let { photos, isQuering, empty, error } = useFetch(brand, isOld);

useEffect(() => {
  if (isOld) {
    const { photos: photosTest } = useFetch(brand, isOld); // can't call a hook here
    photos = photosTest; // can't mutate component-level variables
  }
}, [isOld]);

Upvotes: 0

Danko
Danko

Reputation: 1864

I'd suggest something else:

  1. update your useFetch so it will have refetch function end add it to returned object.
  2. now, your updated hook can be destructured like this: const { photos, isQuering, empty, error, refetch } = useFetch(brand);
  3. your useEfect can be used like this:
  useEffect(() => {
    if(isOld) {
      refetch();
      setIsOld(false)
    }
  }, [isOld]);

Update:

You must rename your custon hook to start with use. Otherwise there is no way for react to differ it from other functions. So, instead of naming it fetch rename it to useFetch.

Upvotes: 4

Related Questions