Reputation: 2087
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
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
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
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