Reputation: 5
please tell me what can be done to save "infinite scrolling" and at the same time so that the search works correctly.
Description of the problem:
I have an array with elements ( filteredPizzas ). I get this array in the Home component and pass it to the PizzaList component already with a certain length by using slice ( it just my implementation of infinite scrolling without API requests )
In the PizzaList component, I filter the passed array by the searchValue and then just display the cards.
The problem is that if the user DOES NOT scroll the page and starts entering the name of some element, it will not be displayed, but this element is definitely in the array it just hasn't been loaded yet by "infinite scrolling". So i want that the search works independently of "infinite scrolling"
App component:
const App = () => {
const [pizzas, setPizzas] = React.useState<Array<Pizza>>([]);
const [filteredPizzas, setFilteredPizzas] = React.useState<Array<Pizza>>([]);
const [selectedCategorie, setSelectedCategorie] = React.useState<number | null>(parseJSON(CATEGORIE_KEY));
const [cart, setCart] = React.useState<Array<Cart>>(parseJSON(CART_KEY) ?? []);
const [currentSort, setCurrentSort] = React.useState<number>(parseJSON(SORT_INDEX_KEY) ?? 0);
const [searchValue, setSearchValue] = React.useState("");
const [loading, setLoading] = React.useState<boolean>(true);
const [errorData, setErrorData] = React.useState<Error | unknown>(null);
React.useEffect(() => {
(async () => {
try {
const { data } = await api.getPizzas();
const sortDirection = initialSortNames[currentSort].sort.includes("-") ? -1 : 1;
setPizzas(data);
setFilteredPizzas(
(typeof selectedCategorie === "number"
? data.filter(({ category }) => category === selectedCategorie)
: data
).sort((a, b) => {
const sortProperty = initialSortNames[currentSort].sort.replace("-", "");
return (
(Number(a[sortProperty as keyof Pizza]) - Number(b[sortProperty as keyof Pizza])) *
sortDirection
);
})
);
} catch (error) {
console.error(error);
setErrorData(error);
} finally {
setLoading(false);
}
})();
}, []);
React.useEffect(() => saveToLocalStorage({ key: CART_KEY, data: JSON.stringify(cart) }), [cart]);
if (errorData || (!loading && !pizzas.length)) {
return (
<NotFound
title='Cannot get data from the server'
description='Please, check your internet connection and refresh the page'
reloadButton
reloadButtonText="Refresh Page"
screen
code={errorData}
/>
);
}
return loading ? (
<Spinner />
) : (
<AppContext.Provider
value={{
loading,
errorData,
pizzas,
filteredPizzas,
setFilteredPizzas,
setPizzas,
cart,
setCart,
selectedCategorie,
setSelectedCategorie,
currentSort,
setCurrentSort,
searchValue,
setSearchValue,
}}
>
<Routing />
</AppContext.Provider>
);
};
export default App;
Home component:
const Home = () => {
const { filteredPizzas } = React.useContext(AppContext);
const [view, setView] = React.useState(6);
const handleScroll = () => {
if (document.documentElement.scrollHeight - (document.documentElement.scrollTop + window.innerHeight) < 100 && view < filteredPizzas.length) {
setView((prevState) => prevState + 3);
}
};
React.useEffect(() => {
document.addEventListener("scroll", handleScroll);
return () => document.removeEventListener("scroll", handleScroll);
}, [view, filteredPizzas]);
return (
<section>
<Container>
<div className="flex flex-col gap-5">
<Tools categories={initialCategories} sortNames={initialSortNames} />
<Title title="All Pizzas" />
</div>
<PizzaList data={filteredPizzas.slice(0, view)} />
</Container>
</section>
);
};
export default Home;
PizzaList component:
const PizzaList: React.FC<Props> = ({ data }) => {
const { searchValue } = React.useContext(AppContext);
const isEmpty = !data.filter(({ title }) => title.toLowerCase().includes(searchValue.toLowerCase())).length;
return (
<ul className='py-[30px] flex flex-wrap justify-between items-center gap-10'>
{isEmpty ? (
<Title title={`${searchValue} not found`} />
) : (
data
.filter(({ title }) => title.toLowerCase().includes(searchValue.toLowerCase()))
.map((item) => (
<li key={item.id}>
<Card {...item} />
</li>
))
)}
</ul>
);
};
export default PizzaList;
Search component:
const Search = () => {
const { setSearchValue } = React.useContext(AppContext);
const [value, setValue] = React.useState("");
const handleChange = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
setValue(value);
handleDelay(value);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
const handleDelay = React.useCallback(debounce((value: string) => setSearchValue(value)), []);
const handleClear = () => {
setValue('');
setSearchValue('');
}
return (
<form className='basis-[300px] relative'>
<Input
classNames='border outline-none focus:border-primary-orange border-solid border-primary-gray py-[5px] px-[40px] box-border rounded-lg w-full'
type='text'
value={value}
onChange={handleChange}
placeholder='Search...'
/>
<button title='search' type='button' className='absolute left-2 top-[50%] translate-y-[-50%]'>
<img src={getImageUrl("search.svg")} alt='search icon' />
</button>
{!!value && (
<button onClick={handleClear} title='clear' type='button' className='absolute right-3 top-[50%] translate-y-[-50%]'>
<img src={getImageUrl("clear.svg")} alt='search icon' width={12} height={12} />
</button>
)}
</form>
);
};
export default Search;
Upvotes: 0
Views: 80
Reputation: 180
Move your filter and search logic into the PizzaList component. The list should simply receive all data + search value, and then apply the handleScroll functionality in that component as well.
Or a quick fix: instead of slicing your pizza data in the Home component, move that into the list component, and simply pass the view property.
Upvotes: 0