Detect bottom of page to fetch more data in react
Asked Answered
C

4

9

I've try several code from several resources to detect if the user already reach the bottom of the page then do fetch more data. Its working but the function always fetch the data whenever I scroll, not whenever the user reach the bottom. Here is my code:

import React, { Component } from 'react';

class DataView extends Component {
  constructor(props) {
    super(props);

    this.state = {
      displayedData: [],
      initialData: [],
      loadingState: false,
    };

    this.fetchMoreData = this.fetchMoreData.bind(this);
    this.handleScroll = this.handleScroll.bind(this);
  }

  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll, true);
  }

  componentWillReceiveProps(props) {
    const initialData = props.initialData;

    this.setState({
      initialData,
      displayedData: initialData.slice(0, 3),
    });
  }

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

  fetchMoreData() {
    console.log('fetch');
    this.setState({ loadingState: true });

    if (this.state.displayedData.length >= this.state.initialData.length) {
      this.setState({
        loadingState: false,
      });
      return;
    }

    const size = 3;
    const initialData = this.state.initialData;
    const copiedinitialData = [...initialData];
    const prevDisplayedData = this.state.displayedData;
    const copiedPrevDisplayedData = [...prevDisplayedData];
    const copiedPrevDisplayedDataLength = copiedPrevDisplayedData.length;
    const nextItem = copiedinitialData.slice(copiedPrevDisplayedDataLength, copiedPrevDisplayedDataLength + size);
    copiedPrevDisplayedData.push(...nextItem);

    setTimeout(() => {
      this.setState({
        displayedData: copiedPrevDisplayedData,
      });
    }, 2000);
    // use set timeout because fetching data still use mock data (not hitting API)
  }

  handleScroll() {
    // FIRST TRIAL
    // const scrollTop = document.getElementById('isScrolling').scrollTop;
    // const clientHeight = document.getElementById('isScrolling').clientHeight;
    // const scrollHeight = document.getElementById('react-element').scrollHeight; // the id is written in another component as the outer element/wrapper

    // console.log('============= ### =============');
    // console.log('scrollTop: ', scrollTop);
    // console.log('clientHeight: ', clientHeight);
    // console.log('scrollHeight: ', scrollHeight);
    // console.log('scrollHeight2: ', scrollHeight2);
    // console.log('============= ### =============');
    // if (scrollTop + clientHeight >= scrollHeight) {
    //  this.fetchMoreData();
    // }
    // above working but always fetch when scrolled, not when user reach bottom

    // SECOND TRIAL
    // const lastDiv = document.querySelector('#isScrolling > div:last-child');
    // const lastDivOffset = lastDiv.offsetTop + lastDiv.clientHeight;
    // const pageOffset = window.pageYOffset + window.innerHeight;
    // console.log('============= ### =============');
    // console.log('lastDivOffset: ', lastDivOffset);
    // console.log('pageOffset: ', pageOffset);
    // console.log('clientHeight: ', document.getElementById('isScrolling').clientHeight);
    // console.log('scrollHeight: ', document.getElementById('isScrolling').scrollHeight);
    // console.log('lastDivOffsetTop: ', lastDiv.offsetTop);
    // console.log((pageOffset / lastDivOffset) * 100);
    // console.log('============= ### =============');

    // const ratio = (pageOffset / lastDivOffset) * 100;
    // let scenario = ratio < 60;
    // let scenario = pageOffset > lastDivOffset - 10;
    // if (scenario) {
    //   this.fetchMoreData();
    // }
    // both of the scenario behave like the first trial

    console.log('============= ### =============');
    console.log('win screen: ', window.screen);
    console.log('win screenTop: ', window.screenTop);
    console.log('win screenLeft: ', window.screenLeft);
    console.log('win screenY: ', window.screenY);
    console.log('win scrollY: ', window.scrollY);
    console.log('win innerHeight: ', window.innerHeight); // always 580
    console.log('win pageYOffset: ', window.pageYOffset);
    console.log('docEl clientHeight: ', document.documentElement.clientHeight);
    console.log('docEl clientLeft: ', document.documentElement.clientLeft);
    console.log('docEl clientTop: ', document.documentElement.clientTop);
    console.log('docEl offsetHeight: ', document.documentElement.offsetHeight);
    console.log('docEl scrollHeight: ', document.documentElement.scrollHeight);
    console.log('docEl scrollLeft: ', document.documentElement.scrollLeft);
    console.log('docEl screenTop: ', document.documentElement.scrollTop);
    console.log('docBody clientHeight: ', document.body.clientHeight);
    console.log('docBody clientLeft: ', document.body.clientLeft);
    console.log('docBody clientTop: ', document.body.clientTop);
    console.log('docBody offsetHeight: ', document.body.offsetHeight);
    console.log('docBody scrollHeight: ', document.body.scrollHeight);
    console.log('docBody scrollLeft: ', document.body.scrollLeft);
    console.log('docBody screenTop: ', document.body.scrollTop);
    console.log('docId scrollHeight: ', document.getElementById('isScrolling').scrollHeight); // ==> A
    console.log('docId screenLeft: ', document.getElementById('isScrolling').scrollLeft);
    console.log('docId scrollTop: ', document.getElementById('isScrolling').scrollTop);
    console.log('docId clientHeight: ', document.getElementById('isScrolling').clientHeight); // ==> B
    console.log('docId offsetHeight: ', document.getElementById('isScrolling').offsetHeight); // ==> C
    // the A value is always 20px greater than B and C. B and C is always same value. The others console.log result is zero
    console.log('============= ### =============');

    if ((window.scrollY + window.innerHeight) >= document.body.scrollHeight) {
      // this if statement also behave like the first trial
      this.fetchMoreData();
    }
  }

  render() {
    const displayedData = this.state.displayedData.map((item, index) => {
      const itemIndex = index;
      const dataName = item.dataName;

      return <div key={itemIndex} style={{ marginBottom: 20 }}>
        {dataName}
             </div>;
    });

    return (
      <div>
        <div className="class">
          <div className="name">
            {/* {another code} */}
          </div>
          <div className="classes">
            {/* {another code} */}
          </div>
          <div id="isScrolling">
            {displayedData}
            {this.state.loadingState
              ? <div> Loading ....</div>
              : <div> No more data available</div>
            }
          </div>
        </div>
      </div>
    );
  }
}
export default DataView;

My objective: when user at certain px from the bottom, this.fetchMoreData will fired. Any help would be very helpful.

Constable answered 3/2, 2019 at 23:38 Comment(3)
Sorry I now see you did try the same thing which should work. Are you sure your page is not to short and is loading up with little or no scroll bar? You say innerHeight is always 580. That is really smallBart
Its loading up with little scroll bar (unless I increase the initial data that I want to display at the first load). the innerHeight is small because I try to use iPhone 5S screen size to view the app. But, what I dont get is why the fetch function always triggered whenever I do scrolling, instead of checking up for the distance. Example, when I scroll (gently) 5 times. Then all of my data fetched, however, Im not going far from the top and the bottom is still far away from my current viewConstable
Have you tried swapping document.body.scrollHeight for document.body.offsetHeight ?? They are kind of the same but deal with padding and borders differently. Also try console logging in your if statement with the result of that calculation so you can see whats going wrong.Bart
B
16

Put this in your componentWillMount()

window.addEventListener('scroll', function() {
   if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
      console.log("you're at the bottom of the page");
      // Show loading spinner and make fetch request to api
   }
});

Don't forget to kill your event Listeners in componentWillUnmount() to prevent memory leaks.

Bart answered 4/2, 2019 at 2:7 Comment(0)
F
4
constructor(props) {
    super(props);
    this.state = {
        height: window.innerHeight,
        message: 'not at bottom'
    };
    this.handleScroll = this.handleScroll.bind(this);
}
handleScroll() {
    const windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
    const body = document.body;
    const html = document.documentElement;
    const docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
    const windowBottom = windowHeight + window.pageYOffset;
    if (windowBottom >= docHeight) {
        this.setState({
            message: 'bottom reached'
        });
    } else {
        this.setState({
            message: 'not at bottom'
        });
    }
}

componentDidMount() {
    window.addEventListener("scroll", this.handleScroll);
}

componentWillUnmount() {
    window.removeEventListener("scroll", this.handleScroll);
}
Feriga answered 4/6, 2020 at 10:11 Comment(0)
M
2

Edited please note this is a react hook solution

useEffect(() => {
        const scrolling_function = () => {
            if((window.innerHeight + window.scrollY) >= document.body.offsetHeight-10){
                console.log("fetching more.........")
                window.removeEventListener('scroll',scrolling_function)
            }
        }
        window.addEventListener('scroll', scrolling_function);
    }, [target_data])
Menam answered 10/4, 2021 at 19:24 Comment(2)
This will not work because it is expressed as a React hook, which is not compatible with the Component class being used in the OP.Keister
this won't work for me because inside this hook the other stateful objects become nullUnwrap
T
0
 useEffect(() => {
   const onScroll = function () {
      if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
        console.log("you're at the bottom of the page")
      }
   }
   window.addEventListener('scroll', onScroll)
   return () => window.removeEventListener('scroll', onScroll)
}, [])
Terraqueous answered 28/2, 2021 at 22:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.