Infinite scrolling with React JS

I am looking at ways to implement infinite scrolling with React. I have come across react-infinite-scroll and found it inefficient as it just adds nodes to the DOM and doesn't remove them. Is there any proven solution with React which will add, remove and maintains constant number of nodes in the DOM.

Here is the jsfiddle problem. In this problem, i want to have only 50 elements in the DOM at a time. others should be loaded and removed as user scrolls up and down. We have started using React because of it's optimization algorithms. Now i couldn't find solution to this problem. I have come across airbnb infinite js. But it is implemented with Jquery. To use this airbnb infinite scroll, i have to loose the React optimisation which i don't want to do.

sample code i want to add scroll is(here i am loading all items. My goal is to load only 50 items at a time)

/** @jsx React.DOM */

var Hello = React.createClass({
    render: function() {
        return (<li>Hello {this.props.name}</li>);
    }
});

var HelloList = React.createClass({ 
     getInitialState: function() {                            
         var numbers =  [];
         for(var i=1;i<10000;i++){
             numbers.push(i);
         }
         return {data:numbers};
     },

    render: function(){
       var response =  this.state.data.map(function(contact){          
          return (<Hello name="World"></Hello>);
        });

        return (<ul>{response}</ul>)
    }
});

React.renderComponent(<HelloList/>, document.getElementById('content'));

Looking for help...

Answers


Basically when scrolling you want to decide which elements are visible and then rerender to display only those elements, with a single spacer element on top and bottom to represent the offscreen elements.

Vjeux made a fiddle here which you can look at:

http://jsfiddle.net/vjeux/KbWJ2/9/

Upon scrolling it executes

scrollState: function(scroll) {
    var visibleStart = Math.floor(scroll / this.state.recordHeight);
    var visibleEnd = Math.min(visibleStart + this.state.recordsPerBody, this.state.total - 1);

    var displayStart = Math.max(0, Math.floor(scroll / this.state.recordHeight) - this.state.recordsPerBody * 1.5);
    var displayEnd = Math.min(displayStart + 4 * this.state.recordsPerBody, this.state.total - 1);

    this.setState({
        visibleStart: visibleStart,
        visibleEnd: visibleEnd,
        displayStart: displayStart,
        displayEnd: displayEnd,
        scroll: scroll
    });
},

and then the render function will display only the rows in the range displayStart..displayEnd.

You may also be interested in ReactJS: Modeling Bi-Directional Infinite Scrolling.


Check out our React Infinite Library:

https://github.com/seatgeek/react-infinite

Update December 2016

I've actually been using react-virtualized in a lot of my projects recently and find that it covers the majority of use cases a lot better. Both libraries are good, it depends on exactly what you're looking for. For instance, react-virtualized supports variable height JIT measuring via an HOC called CellMeasurer, example here https://bvaughn.github.io/react-virtualized/#/components/CellMeasurer.

Update November 2018

A lot of the lessons from react-virtualized have been ported to the smaller, faster, more efficient react-window library from the same author.


Infinite Scroll Concept Using React Js


We can make infinite scroll work by listening the scroll event. We can add an event listener to either the parent most div or even to the window object.

Take a look at the following code

   render() {
        return (
          <div
            className="vc"
            ref="iScroll"
            style={{ height: "420px", overflow: "auto" }}
          >
            <h2>Hurrah! My First React Infinite Scroll</h2>
            <ul>

            </ul>
          </div>
        );
    }

The code above has a simple ul tag; inside which we will bind the fetched items. This tag is surrounded by a div on which we are going to attach an event listener to listen the scroll event.

I hope you are aware of all the tags used here. Aren’t you? Well, some of you may not be familiar with ref! So, ref is used to define the reference to the division (div) or any other html element. By using this reference you can get a hold on that element in react. We have given the reference a name “iScroll” and you can access this div using this.refs.iScroll.

Make sure the height of the div is lesser than the total height of primary showing items else you won’t get the scroll bar. You can set the height to 100% or use the window object instead of our iScroll div to make the scroll at window level.

Now let’s talk about the constructor which will look like this:

constructor(props) {
    super(props);
    this.state = {
      items: 10,
      loadingState: false
    };
}

There are two properties in the state object here; items and loadingState. The items signifies the number of available items that can be included as lis inside the ul section and the loadingState has been used to show a text loading... when data is being loaded. As we are just providing a demo, that’s why used a number as items. In real application you will probably hold your actual list of data there.

After that you will create a function that will render all the items:

displayItems() {
    var items = [];
    for (var k = 0; k < this.state.items; k++) {
      items.push(<li key={k}>Item-VoidCanvas {k}</li>);
    }
    return items;
}

This function creates a list of li and displays all the items. Again, as it’s a demo, we are displaying using the number. In real apps you need to iterate on your items array and display the values it contains.

Update the render function now:

render() {
    return (
      <div
        className="vc"
        ref="iScroll"
        style={{ height: "200px", overflow: "auto" }}
      >
        <h2>Hurrah! My First React Infinite Scroll</h2>
        <ul>
          {this.displayItems()}
        </ul>
        {this.state.loadingState
          ? <p className="loading">
          loading More Items..
        </p>
          : ""}
      </div>
    );
} 

Yes, this is just a normal component showing few lis. How it make it infinitely scrollable? Hope you remember we have used a ref named iScroll earlier, this comes into action now.

componentDidMount() {
    this.refs.iScroll.addEventListener("scroll", () => {
      if (
        this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >=
        this.refs.iScroll.scrollHeight
      ) {
        this.loadMoreItems();
      }
    });
}

As you all know react components has a function componentDidMount() which gets called automatically when the template of that component is rendered into the DOM. And I have used the same function to add the event listener for scroll into our div iScroll. The scrollTop property of the element will find the scroll position and it with the clientHeight property. Next, the if condition will check the addition of these two properties is greater or equal to the scroll-bar height or not. If the condition is true the loadMoreItems function will run.

Here is how the function will look:

loadMoreItems() {
    if(this.state.loadingState){
        return;
    }
    this.setState({ loadingState: true });
    // you may call ajax instead of setTimeout
    setTimeout(() => {
        this.setState({ items: this.state.items + 10, loadingState: false });
    }, 1000);
}

It is pretty simple, first set the loadingState to true and the loading div will be displayed (as shown in the render function). Next, a setTimeout function is called which will increase the number of items by 10 and again make the loadingState false. Here the reason we are using setTimeout is to generate a time delay. In real applications you will probably make an ajax call to your server and on the resolution of that you will perform similar thing which is done by the callback of our setTimeout. Complete code snippet here:

class Layout extends React.Component {
  constructor(props) {
   super(props);
   this.state = {
      items: 10,
      loadingState: false
    };
  }

  componentDidMount() {
    this.refs.iScroll.addEventListener("scroll", () => {
      if (this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >= this.refs.iScroll.scrollHeight - 20){
        this.loadMoreItems();
      }
    });
  }

  displayItems() {
    var items = [];
    for (var i = 0; i < this.state.items; i++) {
      items.push(<li key={i}>Item {i}</li>);
    }
    return items;
  }

  loadMoreItems() {
	 if(this.state.loadingState){
		 return;
	 }
    this.setState({ loadingState: true });
    setTimeout(() => {
      this.setState({ items: this.state.items + 10, loadingState: false });
    }, 1000);
  }

  render() {
    return (
      <div ref="iScroll" style={{ height: "200px", overflow: "auto" }}>
        <ul>
          {this.displayItems()}
        </ul>

        {this.state.loadingState ? <p className="loading"> loading More Items..</p> : ""}

      </div>
    );
  }
}

ReactDOM.render(<Layout />, document.getElementById('example'));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="example"></div>

import React, { Component } from 'react';
import InfiniteScroll from 'react-infinite-scroller';


const api = {
    baseUrl: '/joblist'
};

class Jobs extends Component {
    constructor(props) {
            super(props);
            this.state = {
                listData: [],
                hasMoreItems: true,
                nextHref: null
        };
    }

    fetchData(){
            var self = this;           
            var url = api.baseUrl;
            if(this.state.nextHref) {
                url = this.state.nextHref;
            }

            fetch(url)
            .then( (response) => {
                return response.json() })   
                    .then( (json) => {
                        var list = self.state.listData;                        
                        json.data.map(data => {
                            list.push(data);
                        });

                        if(json.next_page_url != null) {
                            self.setState({
                                nextHref: resp.next_page_url,
                                listData: list                               
                            });
                        } else {
                            self.setState({
                                hasMoreItems: false
                            });
                        }
                    })
                    .catch(error => console.log('err ' + error));

        }
    }

    componentDidMount() {
       this.fetchData();
    }

    render() {
    const loader = <div className="loader">Loading ...</div>;
    let JobItems; 
    if(this.state.listData){  
        JobItems = this.state.listData.map(Job => {
        return (
            <tr>
                <td>{Job.job_number}</td>
                <td>{Job.title}</td>
                <td>{Job.description}</td>
                <td>{Job.status}</td>
            </tr>
        );
      });
    }
    return (
      <div className="Jobs">
        <div className="container">
            <h2>Jobs List</h2>

            <InfiniteScroll
                pageStart={0}
                loadMore={this.fetchData.bind(this)}
                hasMore={this.state.hasMoreItems}
                loader={loader}>
                <table className="table table-bordered">
                <thead>
                    <tr>
                        <th>Job Number</th>
                        <th>Title</th>
                        <th>Description</th>
                        <th>Status</th>
                    </tr>
                </thead>
                <tbody>
                {JobItems}
                </tbody>
                </table>
            </InfiniteScroll>
        </div>
    </div>
    );
  }

}

export default Jobs;

Need Your Help

simple illumination correction in images openCV c++

c++ opencv contrast

I have some color photos and the illumination is not regular in the photos: one side of the image is brighter than the other side.

long double vs double

c++ floating-point long-double

I know that size of various data types can change depending on which system I am on.