0

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;

3 Answers 3

4

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)

Sign up to request clarification or add additional context in comments.

3 Comments

thank you so much @daniel for your well descriptive answer.As you mentioned, using concat in the setState is a bad approach or it leads to further problem,what is the right way to merge the new data with the prev state data ?Please help me with this.
@karthik concat is probably fine, it's just that you were calling this.state.olddata.concat(this.state.olddata) which is just doubling the old data instead of adding the new stuff! My guess is you just need one data element in state (this.state.data) and have that setState call be this.setState({ data: this.state.data.concat(res.data) }). If you need to keep track of old data you can still do that, but from what you have posted here you don't need it
Yeah,i have changed it to newData: this.state.newData.slice().concat(res).it works fine now
0

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

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.