Home > other >  Fetch api in useEffect to make an infinite scroll but execute in wrong order
Fetch api in useEffect to make an infinite scroll but execute in wrong order

Time:01-06

I am trying to fetch data from Algolia database (index.search is similar to fetch) in useEffect,but then I find the order it execute is not the way I think.I console "queryNews1", "queryNews2", ..."queryNews6" in async function queryNews(), and I think they will sequentially print out in console(see image below). But I find that after queryNews2, it "jump out" of queryNews() but execute the code outside queryNews(), after console.log("5"), it go back to execute "queryNews3".

I guess it's an asychronous issue, so I wrap queryNews() inside an another async function const getData = async () => { await queryNews(keyword); }; and call getData(), but it's still execute in wrong way.Why and does anybody know how to fix that??

Sorry for my bad English!

mobile in the console is writing in articleState.map(() => { console.log("mobile"); return (); });

console results image

const [articleState, setArticles] = useState<ArticleType[]>([]);

useEffect(() => {
    console.log("1");

    if (windowResized === "large" || windowResized === undefined) return;

    let isFetching = false;
    let isPaging = true;
    let paging = 0;

    console.log("2");

    async function queryNews(input: string) {
      console.log("queryNews1");

      isFetching = true;

      setIsLoading(true);
      setSearchState(true);
      setPageOnLoad(true);

      console.log("queryNews2");

      const resp = await index.search(`${input}`, {
        page: paging,
      });
      
      console.log("queryNews3");

      const hits = resp?.hits as ArticleType[];

      setTotalArticle(resp?.nbHits);
      
      console.log("queryNews4");

      setArticles((prev) => [...prev, ...hits]);
            
      console.log("queryNews5");

      setIsLoading(false);
            
      console.log("queryNews6");

      paging = paging   1;
      if (paging === resp?.nbPages) {
        isPaging = false;
        setScrolling(true);
        return;
      }

      console.log("queryNews7");

      isFetching = false;

      setSearchState(false);
      setPageOnLoad(false);

      console.log("queryNews8");
    }

    console.log("3");

    async function scrollHandler(e: WheelEvent) {
      if (window.innerHeight   window.scrollY >=
        document.body.offsetHeight - 100) {
        if (isFetching || !isPaging) return;

        console.log("scrollHandler");
        getData();
      }
    }

    const getData = async () => {
      await queryNews(keyword);
    };

    getData()
   
    console.log("4");

    window.addEventListener("wheel", scrollHandler);
    console.log("5");

    return () => {
      window.removeEventListener("wheel", scrollHandler);
    };
  }, [keyword, setSearchState, windowResized]);

CodePudding user response:

Thanks to kim3er, that really help.

But simillar situation happened again when I scroll, it console.log("queryNews2"), and then it console "mobile", which means it render the component again, and then go back to queryNews() to finish execute the rest of the function?Why didn't it wait while I already put all the code in

getData().then(() => {
      console.log("6");
      window.addEventListener("wheel", scrollHandler);
      console.log("7");
    });

Thanks!!

(stack overflow suddenly said I can't embed image now so I paste an image link instead) https://imgur.com/a/lDKEzxy

useEffect(() => {
    console.log("1");
    if (windowResized === "large" || windowResized === undefined) return;
    let isFetching = false;
    let isPaging = true;
    let paging = 0;

    console.log("2");

    async function queryNews(input: string) {
      console.log("queryNews1");
      isFetching = true;
      setIsLoading(true);
      setSearchState(true);
      setPageOnLoad(true);
      console.log("queryNews2");

      const resp = await index.search(`${input}`, {
        page: paging,
      });
      console.log("queryNews3");

      const hits = resp?.hits as ArticleType[];
      setTotalArticle(resp?.nbHits);
      console.log("queryNews4");

      setArticles((prev) => [...prev, ...hits]);
      console.log("queryNews5");

      setIsLoading(false);
      console.log("queryNews6");

      paging = paging   1;
      if (paging === resp?.nbPages) {
        isPaging = false;
        setScrolling(true);
        return;
      }

      console.log("queryNews7");

      isFetching = false;
      setSearchState(false);
      setPageOnLoad(false);
      console.log("queryNews8");
    }

    console.log("3");

    async function scrollHandler(e: WheelEvent) {
      if (
        window.innerHeight   window.scrollY >=
        document.body.offsetHeight - 100
      ) {
        if (isFetching || !isPaging) return;
        console.log("scrollHandler");
         getData().then(() => {
           console.log("6");
           window.addEventListener("wheel", scrollHandler);
           console.log("7");
         });
        
      }
    }

     const getData = async () => {
       await queryNews(keyword);
     };

    getData().then(() => {
      console.log("4");
      window.addEventListener("wheel", scrollHandler);
      console.log("5");
    });

    return () => {
      window.removeEventListener("wheel", scrollHandler);
    };
  }, [keyword, setSearchState, windowResized]);

CodePudding user response:

You're calling getData() without awaiting it. Because of this, it'll run parallel to the top level useEffect code. This isn't an issue, if getData() is the last line of code in the function.

But if you do need that initial getData() to complete before hitting console.log("4");, you could do this:

    getData()
      .then(() => {
        console.log("4");

        window.addEventListener("wheel", scrollHandler);
        console.log("5");
      });

From console.log("4"); will run after the call to getData().

Clarifier on async and .then()

With this function in mind:

const doSomething = async () => {
  // Do something async
  console.log('during');
});

The following:

const asyncFunc = async () => {
  console.log('before');

  await doSomething();

  console.log('after');
});

Is equivalent to:

const asyncFunc = () => {
  console.log('before');

  doSomething()
    .then(() => {
      console.log('after');
    });
});

Either will return:

before
during
after

However, if you used:

const syncFunc =() => {
  console.log('before');

  doSomething();

  console.log('after');
});

Becasue I have not awaited doSomething(), I have created a race condition, whereby during and after could be returned in a different order depending on how long it took the async code to complete. Because the syncFunc script will continue running as soon as doSomething has been called (but crucially, has not finished).

So you could get:

before
after
during

Wrapping await queryNews(keyword); in another function called getData() does not make the function synchronous, it just means that you don't have to keep typing in the keyword parameter. You still need to await getData(), in order to ensure completion before continuing.

  • Related