Skip to content

Instantly share code, notes, and snippets.

@8lane
Created July 13, 2017 16:50
Show Gist options
  • Save 8lane/2cb9f5f30dc663b9762fb1e6105d07f6 to your computer and use it in GitHub Desktop.
Save 8lane/2cb9f5f30dc663b9762fb1e6105d07f6 to your computer and use it in GitHub Desktop.
class InfiniteScroll extends React.Component {
constructor(props) {
super(props);
this.state = {
scrollElement: null,
waypointClassName: 'infinite-scroll__waypoint',
}
this.scrollListener = this.scrollListener.bind(this);
}
componentDidMount() {
this.attachScrollListener();
}
componentWillUnmount() {
this.detachScrollListener();
}
attachScrollListener() {
const scrollElement = this.getScrollParent(this.scrollComponent, this.props.scrollElementName);
this.setState({ scrollElement });
scrollElement.addEventListener('scroll', this.scrollListener);
scrollElement.addEventListener('resize', this.scrollListener);
}
detachScrollListener() {
const { scrollElement } = this.state;
scrollElement.removeEventListener('scroll', this.scrollListener);
scrollElement.removeEventListener('resize', this.scrollListener);
}
getScrollParent(el, className) {
while ((el = el.parentElement) && !el.classList.contains(className));
return el;
}
scrollListener() {
const { scrollElement } = this.state;
const {
currentPage,
clickToUpdate,
hasMore,
loading,
onUpdate,
} = this.props;
const isVisible = this.isWaypointVisible(scrollElement, this.waypointNode);
if (!loading && !clickToUpdate && hasMore && isVisible) {
onUpdate(currentPage + 1);
}
}
isWaypointVisible(container, waypoint) {
const containerRect = container.getBoundingClientRect();
const waypointRect = waypoint.getBoundingClientRect();
return waypointRect.top < (container.offsetHeight + containerRect.top);
}
render() {
const {
children,
currentPage,
loading,
className,
clickToUpdate,
clickToUpdateBtn,
onUpdate
} = this.props;
const { waypointClassName } = this.state;
return (
<div ref={node => { this.scrollComponent = node }} className={`infinite-scroll ${className}`}>
{children}
{loading && children.length > 0 && <Spinner size={40} className="infinite-scroll__loader" />}
{children.length > 0 && !clickToUpdate &&
<span ref={node => { this.waypointNode = node }} className={waypointClassName} />
}
{children.length > 0 && clickToUpdate &&
<button
ref={node => { this.waypointNode = node }}
className={waypointClassName}
disabled={loading}
onClick={() => onUpdate(currentPage + 1)}>
{this.props.clickToUpdateBtn}
</button>
}
</div>
);
}
}
InfiniteScroll.defaultProps = {
className: '',
children: [],
hasMore: true,
currentPage: 1,
clickToUpdate: false,
clickToUpdateBtn: 'Load more...',
}
InfiniteScroll.propTypes = {
children: React.PropTypes.array,
hasMore: React.PropTypes.bool,
scrollElement: React.PropTypes.string,
currentPage: React.PropTypes.number,
className: React.PropTypes.string,
clickToUpdate: React.PropTypes.bool,
clickToUpdateBtn: React.PropTypes.string,
loading: React.PropTypes.bool.isRequired,
onUpdate: React.PropTypes.func.isRequired,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment