Skip to content

Instantly share code, notes, and snippets.

@MrMjauh
Last active June 3, 2019 17:54
Show Gist options
  • Save MrMjauh/b233bfc9a09d0271b13071e12b0a0a20 to your computer and use it in GitHub Desktop.
Save MrMjauh/b233bfc9a09d0271b13071e12b0a0a20 to your computer and use it in GitHub Desktop.
Pagination and infinite scroll
import * as React from "react";
import { RefObject } from "react";
import { IContainerComponentProps } from "../../model/ui/IContainerComponentProps";
import { Backoff } from "../../services/util/Backoff";
import { DynamicIntervalHandler } from "../../services/util/VariableIntervaller";
const classnames = require("classnames");
const styles = require("./style.scss");
interface Props<T> extends IContainerComponentProps {
lastSyncedPage: T;
offsetTriggerScroll: number;
requestPage: (page: T) => void;
nextPage(): T | undefined;
}
interface State<T> {
lastPage: T;
}
export default class Pagination<T> extends React.Component<Props<T>, State<T>> {
private intervallID: number | undefined;
private readonly scrollContainerRef: RefObject<HTMLDivElement> = React.createRef();
private readonly backOffStrat: Backoff = new Backoff(50, 1000);
public static readonly defaultProps = {
offsetTriggerScroll: 400,
};
constructor(props: Props<T>) {
super(props);
this.state = {
lastPage: this.props.lastSyncedPage
};
this.startIntervall = this.startIntervall.bind(this);
}
public componentDidMount() {
this.startIntervall();
}
private startIntervall() {
if (this.intervallID != null) {
DynamicIntervalHandler.flagForRemoval(this.intervallID);
}
this.intervallID = DynamicIntervalHandler.start(() => {
if (this.scrollContainerRef.current != null) {
if (this.shouldRequestNewData()) {
// Page are not synced, no need to request anything
const next: T | undefined = this.props.nextPage();
if (this.props.lastSyncedPage !== this.state.lastPage || next == null) {
this.backOffStrat.backoff();
} else {
this.backOffStrat.reset();
this.requestNewData(next);
}
} else {
this.backOffStrat.backoff();
}
}
}, () => this.backOffStrat.getSleepTime());
}
public componentWillUnmount() {
if (this.intervallID != null) {
DynamicIntervalHandler.flagForRemoval(this.intervallID);
}
}
public reset(state: T): void {
if (this.intervallID != null) {
DynamicIntervalHandler.flagForRemoval(this.intervallID);
}
this.backOffStrat.reset();
this.setState({
lastPage: state
}, () => this.startIntervall());
}
private isScrollable(): boolean | undefined {
if (this.scrollContainerRef.current == null) {
return undefined;
}
return this.scrollContainerRef.current.scrollHeight > this.scrollContainerRef.current.clientHeight ||
this.scrollContainerRef.current.scrollWidth > this.scrollContainerRef.current.clientWidth;
}
private shouldRequestNewData(): boolean {
if (this.scrollContainerRef.current == null) {
return false;
} else if (!this.isScrollable()) {
return true;
} else if ((this.scrollContainerRef.current.scrollTop + this.scrollContainerRef.current.clientHeight) >=
(Math.max(this.scrollContainerRef.current.scrollHeight - this.props.offsetTriggerScroll, 0))) {
return true;
}
return false;
}
private requestNewData(next: T): void {
this.setState({ lastPage: next }, () => {
this.props.requestPage(this.state.lastPage);
});
}
public render() {
return (
<div
ref={this.scrollContainerRef}
className={classnames(styles.container, this.props.className)}
>
{this.props.children}
</div>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment