text: import React, { Component } from 'react';
  import PropTypes from 'prop-types';
  
  export default class InfiniteScroll extends Component {
    static propTypes = {
      children: PropTypes.node.isRequired,
      element: PropTypes.node,
      hasMore: PropTypes.bool,
      initialLoad: PropTypes.bool,
      isReverse: PropTypes.bool,
      loader: PropTypes.node,
      loadMore: PropTypes.func.isRequired,
      pageStart: PropTypes.number,
      ref: PropTypes.func,
      getScrollParent: PropTypes.func,
      threshold: PropTypes.number,
      useCapture: PropTypes.bool,
      useWindow: PropTypes.bool
    };
  
    static defaultProps = {
      element: 'div',
      hasMore: false,
      initialLoad: true,
      pageStart: 0,
      ref: null,
      threshold: 250,
      useWindow: true,
      isReverse: false,
      useCapture: false,
      loader: null,
      getScrollParent: null
    };
  
    constructor(props) {
      super(props);
  
      this.scrollListener = this.scrollListener.bind(this);
      this.eventListenerOptions = this.eventListenerOptions.bind(this);
      this.mousewheelListener = this.mousewheelListener.bind(this);
    }
  
    componentDidMount() {
      this.pageLoaded = this.props.pageStart;
      this.options = this.eventListenerOptions();
      this.attachScrollListener();
    }
  
    componentDidUpdate() {
      if (this.props.isReverse && this.loadMore) {
        const parentElement = this.getParentElement(this.scrollComponent);
        parentElement.scrollTop =
          parentElement.scrollHeight -
          this.beforeScrollHeight +
          this.beforeScrollTop;
        this.loadMore = false;
      }
      this.attachScrollListener();
    }
  
    componentWillUnmount() {
      this.detachScrollListener();
      this.detachMousewheelListener();
    }
  
    isPassiveSupported() {
      let passive = false;
  
      const testOptions = {
        get passive() {
          passive = true;
        }
      };
  
      try {
        document.addEventListener('test', null, testOptions);
        document.removeEventListener('test', null, testOptions);
      } catch (e) {
        // ignore
      }
      return passive;
    }
  
    eventListenerOptions() {
      let options = this.props.useCapture;
  
      if (this.isPassiveSupported()) {
        options = {
          useCapture: this.props.useCapture,
          passive: true
        };
      } else {
        options = {
          passive: false
        };
      }
      return options;
    }
  
    // Set a defaut loader for all your `InfiniteScroll` components
    setDefaultLoader(loader) {
      this.defaultLoader = loader;
    }
  
    detachMousewheelListener() {
      let scrollEl = window;
      if (this.props.useWindow === false) {
        scrollEl = this.scrollComponent.parentNode;
      }
  
      scrollEl.removeEventListener(
        'mousewheel',
        this.mousewheelListener,
        this.options ? this.options : this.props.useCapture
      );
    }
  
    detachScrollListener() {
      let scrollEl = window;
      if (this.props.useWindow === false) {
        scrollEl = this.getParentElement(this.scrollComponent);
      }
  
      scrollEl.removeEventListener(
        'scroll',
        this.scrollListener,
        this.options ? this.options : this.props.useCapture
      );
      scrollEl.removeEventListener(
        'resize',
        this.scrollListener,
        this.options ? this.options : this.props.useCapture
      );
    }
  
    getParentElement(el) {
      const scrollParent =
        this.props.getScrollParent && this.props.getScrollParent();
      if (scrollParent != null) {
        return scrollParent;
      }
      return el && el.parentNode;
    }
  
    filterProps(props) {
      return props;
    }
  
    attachScrollListener() {
      const parentElement = this.getParentElement(this.scrollComponent);
  
      if (!this.props.hasMore || !parentElement) {
        return;
      }
  
      let scrollEl = window;
      if (this.props.useWindow === false) {
        scrollEl = parentElement;
      }
  
      scrollEl.addEventListener(
        'mousewheel',
        this.mousewheelListener,
        this.options ? this.options : this.props.useCapture
      );
      scrollEl.addEventListener(
        'scroll',
        this.scrollListener,
        this.options ? this.options : this.props.useCapture
      );
      scrollEl.addEventListener(
        'resize',
        this.scrollListener,
        this.options ? this.options : this.props.useCapture
      );
  
      if (this.props.initialLoad) {
        this.scrollListener();
      }
    }
  
    mousewheelListener(e) {
      // Prevents Chrome hangups
      // See: https://stackoverflow.com/questions/47524205/random-high-content-download-time-in-chrome/47684257#47684257
      if (e.deltaY === 1 && !this.isPassiveSupported()) {
        e.preventDefault();
      }
    }
  
    scrollListener() {
      const el = this.scrollComponent;
      const scrollEl = window;
      const parentNode = this.getParentElement(el);
  
      let offset;
      if (this.props.useWindow) {
        const doc =
          document.documentElement || document.body.parentNode || document.body;
        const scrollTop =
          scrollEl.pageYOffset !== undefined
            ? scrollEl.pageYOffset
            : doc.scrollTop;
        if (this.props.isReverse) {
          offset = scrollTop;
        } else {
          offset = this.calculateOffset(el, scrollTop);
        }
      } else if (this.props.isReverse) {
        offset = parentNode.scrollTop;
      } else {
        offset = el.scrollHeight - parentNode.scrollTop - parentNode.clientHeight;
      }
  
      // Here we make sure the element is visible as well as checking the offset
      if (
        offset < Number(this.props.threshold) &&
        (el && el.offsetParent !== null)
      ) {
        this.detachScrollListener();
        this.beforeScrollHeight = parentNode.scrollHeight;
        this.beforeScrollTop = parentNode.scrollTop;
        // Call loadMore after detachScrollListener to allow for non-async loadMore functions
        if (typeof this.props.loadMore === 'function') {
          this.props.loadMore((this.pageLoaded += 1));
          this.loadMore = true;
        }
      }
    }
  
    calculateOffset(el, scrollTop) {
      if (!el) {
        return 0;
      }
  
      return (
        this.calculateTopPosition(el) +
        (el.offsetHeight - scrollTop - window.innerHeight)
      );
    }
  
    calculateTopPosition(el) {
      if (!el) {
        return 0;
      }
      return el.offsetTop + this.calculateTopPosition(el.offsetParent);
    }
  
    render() {
      const renderProps = this.filterProps(this.props);
      const {
        children,
        element,
        hasMore,
        initialLoad,
        isReverse,
        loader,
        loadMore,
        pageStart,
        ref,
        threshold,
        useCapture,
        useWindow,
        getScrollParent,
        ...props
      } = renderProps;
  
      props.ref = node => {
        this.scrollComponent = node;
        if (ref) {
          ref(node);
        }
      };
  
      const childrenArray = [children];
      if (hasMore) {
        if (loader) {
          isReverse ? childrenArray.unshift(loader) : childrenArray.push(loader);
        } else if (this.defaultLoader) {
          isReverse
            ? childrenArray.unshift(this.defaultLoader)
            : childrenArray.push(this.defaultLoader);
        }
      }
      return React.createElement(element, props, childrenArray);
    }
  }
  