0

I want to infinite scroll to fetch any items, the brief step is:

1) scroll at the end of screen.

2) use redux to update items pageIndex.

3) use componentDidUpdate to catch this redux action.

4) append the fetch items to the end of items' array.

class InfiniteScroll extends React.Component {
  componentDidMount() {
    window.addEventListener("scroll", throttle(this.onScroll, 500), false);
  }
  componentWillUnmount() {
    window.removeEventListener("scroll", this.onScroll, false);
  }
  componentDidUpdate(preProps) {
    if (props.pageindex === preProps.pageindex + 1)
      fetchItems(...);
  }
  onScroll = () => {
    if (
      window.innerHeight + window.scrollY >=
      document.body.offsetHeight - 100
    ) {
      updatePage(...);
    }
  };
  render() {
    return <Component {...this.props} />;
  }
}

The only problem with this code is when updatePage executes once, the fetchItems can not follow it immediately.

ccd
  • 2,488
  • 1
  • 15
  • 38
  • What do you mean by "`fetchItems` can not follow it immediately"? Can you show more of your code (e.g. what `updatePage` and `fetchItems` are doing)? – Tadas Antanavicius Jul 13 '18 at 15:58
  • Infinite scroll to fetch thing is just like onPaginated to show the items, except you will be intend to scroll at the end of page instead of Clicking the `next-page` button. – ccd Jul 14 '18 at 00:55

2 Answers2

2

Since you are using redux, your list of items should be controlled via actions/reducer.

InfiniteScroll.js:

onScroll () {
    var nearBottom = window.innerHeight + window.scrollY >= document.body.offsetHeight - 100;
    if (!this.props.fetching && nearBottom) {
        fetchItems();  // dispatch action
    }
}

render () {
    var items = this.props.items.map(item => (<Component {item}/>)

    return (
        <ul>{ items }</ul>
    )
}

componentDidMount() {
    window.addEventListener("scroll", this.onScroll.bind(this), false);
}
componentWillUnmount() {
    window.removeEventListener("scroll", this.onScroll.bind(this), false);
}

actions:

fetchItems () {
    return dispatch => {
        dispatch({
            type: "FETCH_ITEMS"
        });
        fetch("/api/items")
        .then(items => {
            dispatch(itemsReceived(items));
        });
    }
}
itemsReceived (items) {
    return {
        type: "ITEMS_RECEIVED",
        payload: {
            items: items
        }
    }
}

reducer:

case "FETCH_ITEMS":
    return {
        ...prevState,
        fetching: true

case "ITEMS_RECEIVED":
    return {
        ...prevState,
        fetching: false,
        items: prevState.items.concat(items)
    }

This way, a clearly defined action (FETCH_ITEMS) is triggered by the scroll. Using redux-thunk, the action makes an API call to fetch the new items. When that call is complete, you dispatch a new action to feed the new items through the reducer.

The reducer then updates the state, causing <InfiniteScroll> to rerender with an updated list of items.

NOTE: you should also set fetching to false in the case of an error.

thedarklord47
  • 2,494
  • 2
  • 17
  • 45
  • This is very likely with my reducer, but fetchItems will execute many times when onScroll is triggered. You can use lodash's throttle to limit the execution once, however it also have a problem. – ccd Jul 14 '18 at 01:19
  • one thing you could do is force the throttling behaviour whenever a fetch is currently in progress. This is easy to do in your reducer. See example above. Notice how whenever a fetch is in progress `fetching` will be true, preventing another request from firing off. Once the items are received, the page is updated and scrolling will once again fire fetch requests. – thedarklord47 Jul 17 '18 at 20:01
0

If I were you, I'd use a library to do the heavy lifting for me.

One option is react-infinite-scroller.

Here's how your use-case might look like, in mostly pseudo-code:

<InfiniteScroll
    pageStart={0}
    loadMore={fetchItems} // that's your method for loading more results
    hasMore={props.pageindex === preProps.pageindex + 1} // your 'has more' logic
    loader={<div className="loader">Loading ...</div>}
>
    {items} // <-- This is the content you want to load
</InfiniteScroll>

For a working example, check out the demo in their repo.


The other option is to try to debug your code. But you need to provide more context. From the info you provided, it's really hard to tell what's wrong.

Kaloyan Kosev
  • 10,232
  • 4
  • 48
  • 82
  • I will try to complete it first by myself, sorry for it. – ccd Jul 14 '18 at 01:05
  • 1
    Okay. I want to help you, but I don't have enough context. Update your question and explain what exactly is the problem. Try to make a complete example and somebody might be able to debug it. – Kaloyan Kosev Jul 14 '18 at 10:26