react-infinite-scroller
react-infinite-scroller copied to clipboard
Having troubles using custom state management, loadMore called multiple times?
I'm using a custom state management instead of using the page
parameter for loading items. This because I need to "reset" the page after some search parameters changes.
Basically this is what I'm storing:
const [pagination, setPagination] = useState({ items: [], hasMore: true, loading: true, error: false, nextPage: 1 });
... and this is my loadMore
function:
const loadMore = () => {
const fetch = async () => {
try {
// Fetch the items passing the "page" parameter from the state
const { items, headers } = await axios.get(apiUrl, { params: { ...params, page: pagination.nextPage } })
.then(({ data, headers }) => ({ items: data, headers }));
setPagination(prev => ({ ...prev,
items: [...prev.items, ...items], // Append new items to the existing ones (this is how to component works)
hasMore: pagination.nextPage < parseInt(headers['x-total-pages']), // X-Total-Pages comes from the server
nextPage: pagination.nextPage + 1,
}));
} catch (error) {
setPagination(prev => ({ ...prev, error: false }));
} finally {
setPagination(prev => ({ ...prev, loading: false }));
}
};
fetch();
};
That is, after a successfully fetch, update the nextPage
parameter and hasMore
.
What is happening is strange: on the first page load I have multiple calls with the very same nextPage
value:
The component isn't very complex or special:
<div style={{ height: '700px', overflow: 'auto' }} ref={scrollContainer}>
<InfiniteScroll
pageStart={0}
loadMore={loadMore}
hasMore={pagination.hasMore}
useWindow={false}
getScrollParent={() => scrollContainer.current}
loader={
<div key={0} className="flex flex-col items-center content-center w-full p-3">
<HalfCircleSpinner color="red"/>
</div>
}>
{pagination.items.map(item => <p key={item.id}>#{item.id} {item.name}</p>)}
</InfiniteScroll>
</div>
I used another variable call "isAPICall". then I set it to true before my API call and then set again it to false in end of the api call. before start the api call i check that variable is false.
const [isAPICall, setStartAPICall] = useState(false);
let loadData=()=>{
if(!hasMore || isAPICall) return;
setStartAPICall(true);
get(`url`).then((response: any) => {
if (response.data.statusCode === 200) {
if (response.data.result != null) {
setPendingCampaignList(prevPendingCampaignList=> [...prevPendingCampaignList, ...response.data.result]);
setOffset(prevOffset => prevOffset + limit);
setHasMore(response.data.result.length === limit);
} else {
let messageBox = {
show: true,
title: "Error",
className: "error",
content: "Error While Getting the Data",
isConfirmation: false,
callBackFunction: null
}
dispatch(showMessageBox(messageBox));
}
} else {
let errorType = response.data.errorList[0].errorType;
let messageBox = {
show: true,
title: ErrorTypes.Error === errorType ? "Error" : "Warning",
className: ErrorTypes.Error === errorType ? "error" : "warning",
content: response.data.errorList[0].statusMessage,
isConfirmation: false,
callBackFunction: null
}
dispatch(showMessageBox(messageBox));
}
setStartAPICall(false);
});
}
Very true.
I faced a similar problem when implementing InfiniteScroll
with Apollo v3 pagination.
I ended up just using the loading
from Apollo, as well as by checking the network status.
I guess the trigger for loadMore
doesn't really have any idea if the previous request is still active.
EDIT: I probably should have been more clear on my solution. For me, I had to keep track of the loading state manually and only load more if loading is false. See the loadMoreProducts
function below.
import { gql, useQuery, NetworkStatus } from '@apollo/client'
import InfiniteScroll from 'react-infinite-scroller'
export const ALL_PRODUCTS_QUERY = gql`
query products($start: Int!, $limit: Int!) {
products(start: $start, limit: $limit) {
id
name
}
productsCount
}
`
export const productsQueryVars = {
start: 0,
limit: 12
}
export default function Products() {
const { loading, error, data, fetchMore, networkStatus } = useQuery(
ALL_PRODUCTS_QUERY,
{
variables: productsQueryVars,
// Setting this value to true will make the component rerender when
// the "networkStatus" changes, so we are able to know if it is fetching
// more data
notifyOnNetworkStatusChange: true
}
)
const loadingMoreProducts = networkStatus === NetworkStatus.fetchMore
if (error) return <div>Error</div>
if (loading && !loadingMoreProducts) return <div>Loading</div>
const { products, productsCount } = data
const areMoreProducts = productsCount > products.length + 1
const loadMoreProducts = () => {
if (loading || loadingMoreProducts) return
fetchMore({
variables: {
start: products.length,
limit: 12
}
})
}
const loader = <div>Loading...</div>
return (
<section>
<InfiniteScroll
pageStart={0}
loadMore={loadMoreProducts}
hasMore={areMoreProducts}
loader={loader}
>
<ul>
{products.map((product, index) => (
<li key={product.id} style={{ height: 100, border: '1px solid' }}>
<div>{product.name}</div>
</li>
))}
</ul>
</InfiniteScroll>
</section>
)
}
I have very similar issue. Looks like loadMore
is firing continuously till the result is received.
i have same problem, please help me
same problem
I have the same problem, but I set hasMore=false while loading and set it back to true after load which solved this problem.
@Surangaup 's solution worked for me :slightly_smiling_face: