Infinite Scroll Component not showing fetching items while using React Hooks

I’m doing an instant search functionality for food recipes and want to implement Infinite Scroll Component. What I want is to present a search bar as beginning, with no recipes show at all, then when user starts to type letter, it will fetch and show 8 items from data fetching from API, and when scroll down at bottom page it will continue fetch and show the next 8 items.

I’m following instruction from this link, but I encounter two issues:

  • If I set initial useState follow the instruction, it shows 8 empty placeholder (no data)

  • If I scroll down, it will fetch and show the next 8 empty placeholder with no data

If I set initial useState as empty array [], it works fine to fetch and show every placeholder with data in each, so what I understand about this maybe is the code Array.from(Array(8).keys(), n => n + 1) in useState and Array.from(Array(8).keys(), n => n + prevState.length + 1) in function fetchMoreListItems() are not relevant to my case.

My question is how to implement the right code so it can fetch the right data in number of object I want to, so it does with the scroll bar when I scroll down as well. Thank you Everyone!

Here’s my demo: gif

Here’s my code:

// Recipes.js

import React, { useState, useEffect } from "react"
import Axios from "axios"
import RecipeCard from "../components/RecipeCard"
import SearchBar from "../components/SearchBar"
import "../style/Recipes.css"

export default function Recipes() {
  
  const [isLoading, setIsLoading] = useState(false)
  const [query, setQuery] = useState("")
  const [recipes, setRecipes] = useState(Array.from(Array(8).keys(), n => n + 1))
  const [isFetching, setIsFetching] = useState(false)

  const url = `https://www.themealdb.com/api/json/v1/1/search.php?s=${query}`
    
    // function to search for the recipes when enter `search button`
    const searchRecipes = async () => {
      if (query !== "") {
        setIsLoading(true)
        const result = await Axios.get(url)
        console.log(result)
        setRecipes(result.data.meals)
        setQuery("")
        setIsLoading(false)
      } 
    }

    // do instant search when start typing any of letters
    useEffect(async () => {
      if (query !== "") {
        const result = await Axios.get(url)
        console.log(result);
        setRecipes(result.data.meals)
      }
    }, [query])

    // handle handleScroll
    useEffect(() => {
      window.addEventListener('scroll', handleScroll);
      return () => window.removeEventListener('scroll', handleScroll);
    }, [])

    function handleScroll() {
      if (window.innerHeight + document.documentElement.scrollTop + 1 >= document.documentElement.offsetHeight) return
      setIsFetching(true)
    }

    useEffect(() => {
      if (!isFetching) return
      fetchMoreListItems()
    }, [isFetching])

    function fetchMoreListItems() {
      setTimeout(() => {
        setRecipes(prevState => ([...prevState, ...Array.from(Array(8).keys(), n => n + prevState.length + 1)]))
        setIsFetching(false)
      }, 2000);
    }

    const onChange = async e => {
      setQuery(e.target.value)
    }

    const handleSubmit = e => {
        e.preventDefault()
        searchRecipes()
    }

    const clearInput = () => {
      setRecipes([])
      setQuery("")
    }

    return (
        <div className="recipes">
            <div className="search-box">
              <h1>Recipe App</h1>
              <SearchBar
                  handleSubmit={handleSubmit}
                  value={query}
                  name="name"
                  onChange={onChange}
                  isLoading={isLoading}
              />
              {
                query.length !== 0 && 
                  <div className="close-icon" onClick={clearInput}>
                    <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 1024 1024" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg>
                  </div>
              }
              <div className="search-result">
                {
                  recipes && query !== "" &&
                    recipes.slice(0, 5).map((val) => {
                      return (
                        <a className="search-item" href={val.strSource} target="_blank" rel="noopener noreferrer">
                          <p>{val.strMeal}</p>
                        </a>
                      )
                    })
                }
              </div>
            </div>
            <div className="recipes-container">
              {
                recipes && query !== null ?
                  recipes.map((recipe) => (
                      <RecipeCard 
                          key={recipe.idMeal}
                          recipe={recipe}
                      />
                  ))
                  : "We're sorry! No recipe found."
              }
            </div>
            {isFetching && 'Fetching more recipes...'}
        </div>
    )
}