Last active
June 3, 2019 17:54
-
-
Save MrMjauh/b233bfc9a09d0271b13071e12b0a0a20 to your computer and use it in GitHub Desktop.
Pagination and infinite scroll
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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