Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Having troubles using custom state management, loadMore called multiple times? #261

Open
gremo opened this issue Sep 13, 2020 · 7 comments

Comments

@gremo
Copy link

gremo commented Sep 13, 2020

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:

Cattura

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>
@gremo gremo changed the title Having troubles using custom state management, loadMore called multiple time? Having troubles using custom state management, loadMore called multiple times? Sep 13, 2020
@Surangaup
Copy link

Surangaup commented Sep 20, 2020

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

        });
    
    }

@benjaminadk
Copy link

benjaminadk commented Oct 9, 2020

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

@petrovy4
Copy link

I have very similar issue. Looks like loadMore is firing continuously till the result is received.

@arindamsamanta748
Copy link

i have same problem, please help me

@abhishkekalia
Copy link

same problem

@foxty
Copy link

foxty commented Feb 9, 2021

I have the same problem, but I set hasMore=false while loading and set it back to true after load which solved this problem.

@wnikola
Copy link

wnikola commented May 28, 2021

@Surangaup 's solution worked for me 🙂

ursu29 pushed a commit to ursu29/untitled1 that referenced this issue Oct 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants