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>
)
}