CodeZombie
CodeZombie

Reputation: 2087

Reactjs:Infinite Scroll not working

I'm trying to add infinite scroll in my web application.When the user scrolls down the page, there must be an API call to load the data beneath the existing data.So, the problem here is when I reach the bottom of the web page, the API is not being called.

import React from "react";
import { Link } from 'react-router-dom';
import axios from 'axios';

 class InfiniteData extends React.Component{
constructor(props){
super(props);
this.state={olddata: [],newData: [], requestSent: false}
}
componentDidMount(){
  window.addEventListener('scroll', this.handleOnScroll);
  this.doQuery();
}

componentWillUnmount() {
  window.removeEventListener('scroll', this.handleOnScroll);
}

doQuery() {
  console.log("indoquery");
  axios.get("https://jsonplaceholder.typicode.com/posts")
  .then( res=>
  this.setState({
    olddata: res.data,
    newData: this.state.olddata.concat(this.state.olddata)
  })
  )
  .catch(console.log("error"))
}
handleOnScroll(){
  var scrollTop = (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;
  var scrollHeight = (document.documentElement && document.documentElement.scrollHeight) || document.body.scrollHeight;
  var clientHeight = document.documentElement.clientHeight || window.innerHeight;
  var scrolledToBottom = Math.ceil(scrollTop + clientHeight) >= scrollHeight;

  if (scrolledToBottom) {
     console.log("At bottom");
    // enumerate a slow query
     setTimeout(this.doQuery, 2000);
  }
}
  render()
  {
    return (
    <div>
      <div className="data-container">
        {this.state.newData && this.state.newData.map((dat,i)=>
          <div key={i}>
            {dat.body}
          </div>
        )}
      </div>
    </div>
    );
  }
}

export default InfiniteData;

Upvotes: 0

Views: 3343

Answers (3)

Reason
Reason

Reputation: 1430

As Daniel Thompson stated, the problem is that you do not bind this. To elaborate: when the eventListener invokes the function this will not be bound to your component, but rather to window.

Binding this in the event listener will solve the problem of the code not being called, but will create a new problem. When you call handleOnScroll.bind(this) a new function is created, so when you try to un-register it, it will not unregister. To get both registration and unregistration to work as expected, save that new function and use it to register/unregister.

constructor(props){
    super(props);
    // Need to bind 'this' to refer to component.
    // This will create a new function which will have to be stored so it can be unregistered
    this.scrollListener = this.handleOnScroll.bind(this);
    this.state={olddata: [],newData: [], requestSent: false}
}

componentDidMount(){
    window.addEventListener('scroll', this.scrollListener);
    this.doQuery();
}

componentWillUnmount() {
    window.removeEventListener('scroll', this.scrollListener);
}

Upvotes: 0

Daniel Thompson
Daniel Thompson

Reputation: 536

This is actually just an obscured case of not binding this correctly: the following line calls handleOnScroll using window (not the InfiniteData component instance) as this:

window.addEventListener('scroll', this.handleOnScroll);

Then, your setTimeout call is trying to call this.doQuery, which is undefined since window.doQuery doesn't exist.

If you bind this correctly for the EventListener, this should work out: either change to window.addEventListener('scroll', this.handleOnScroll.bind(this)); in componentDidMount or add the following line inside the constructor to keep it bound across the board:

this.handleOnScroll = this.handleOnScroll.bind(this)

Note: this isn't the problem you're having, but be careful inside your setState call--do you mean to use newData: this.state.olddata.concat(res.data)? (passing res.data to the concat call)

Upvotes: 4

mu_sa
mu_sa

Reputation: 2725

Following should work.

import React from "react";

function doQuery() {
  console.log("indoquery");
  // Do something here
}
class InfiniteData extends React.Component{
    constructor(props){
        super(props);
        this.state={olddata: [],newData: [], requestSent: false}
    }
    componentDidMount(){
        window.addEventListener('scroll', this.handleOnScroll);
        doQuery();
    }

    componentWillUnmount() {
        window.removeEventListener('scroll', this.handleOnScroll);
    }


    handleOnScroll(){
        // ..... 
       if (scrolledToBottom) {
           setTimeout(function () {doQuery()}, 2000);
       }
    }
    render() {
        return (
           <div>

           </div>
        );
    }
}

export default InfiniteData;

Note that I have removed some code to make it compact and easier for you to understand where the problem lies. Basically the fix is where you define the doQuery function and how you pass that function to setTimeout

Upvotes: 0

Related Questions