perf: Use progressive rendering on PaginatedList component (#1156)

* Use progressive rendering on PaginatedList component
Move drafts and starred views to paginated

* heading
This commit is contained in:
Tom Moor
2020-01-13 18:17:56 -08:00
committed by GitHub
parent 5b78cb8963
commit 22230c25e5
4 changed files with 63 additions and 126 deletions

View File

@@ -1,12 +1,9 @@
// @flow
import * as React from 'react';
import { observable, action } from 'mobx';
import { observer } from 'mobx-react';
import { Waypoint } from 'react-waypoint';
import { DEFAULT_PAGINATION_LIMIT } from 'stores/BaseStore';
import Document from 'models/Document';
import DocumentList from 'components/DocumentList';
import { ListPlaceholder } from 'components/LoadingPlaceholder';
import DocumentPreview from 'components/DocumentPreview';
import PaginatedList from 'components/PaginatedList';
type Props = {
documents: Document[],
@@ -18,79 +15,20 @@ type Props = {
@observer
class PaginatedDocumentList extends React.Component<Props> {
isInitiallyLoaded: boolean = false;
@observable isLoaded: boolean = false;
@observable isFetchingMore: boolean = false;
@observable isFetching: boolean = false;
@observable offset: number = 0;
@observable allowLoadMore: boolean = true;
componentDidMount() {
this.isInitiallyLoaded = !!this.props.documents.length;
this.fetchResults();
}
componentDidUpdate(prevProps: Props) {
if (prevProps.fetch !== this.props.fetch) {
this.fetchResults();
}
}
fetchResults = async () => {
this.isFetching = true;
const limit = DEFAULT_PAGINATION_LIMIT;
const results = await this.props.fetch({
limit,
offset: this.offset,
...this.props.options,
});
if (
results &&
(results.length === 0 || results.length < DEFAULT_PAGINATION_LIMIT)
) {
this.allowLoadMore = false;
} else {
this.offset += DEFAULT_PAGINATION_LIMIT;
}
this.isLoaded = true;
this.isFetching = false;
this.isFetchingMore = false;
};
@action
loadMoreResults = async () => {
// Don't paginate if there aren't more results or were in the middle of fetching
if (!this.allowLoadMore || this.isFetching) return;
this.isFetchingMore = true;
await this.fetchResults();
};
render() {
const { empty, heading, documents, fetch, options, ...rest } = this.props;
const showLoading =
this.isFetching && !this.isFetchingMore && !this.isInitiallyLoaded;
const showEmpty = !documents.length && !showLoading;
const showList =
(this.isLoaded || this.isInitiallyLoaded) && !showLoading && !showEmpty;
return (
<React.Fragment>
{showEmpty && empty}
{showList && (
<React.Fragment>
{heading}
<DocumentList documents={documents} {...rest} />
{this.allowLoadMore && (
<Waypoint key={this.offset} onEnter={this.loadMoreResults} />
)}
</React.Fragment>
<PaginatedList
items={documents}
empty={empty}
heading={heading}
fetch={fetch}
options={options}
renderItem={item => (
<DocumentPreview key={item.id} document={item} {...rest} />
)}
{showLoading && <ListPlaceholder count={5} />}
</React.Fragment>
/>
);
}
}

View File

@@ -11,6 +11,7 @@ import { ListPlaceholder } from 'components/LoadingPlaceholder';
type Props = {
fetch?: (options: ?Object) => Promise<void>,
options?: Object,
heading?: React.Node,
empty?: React.Node,
items: any[],
renderItem: any => React.Node,
@@ -22,6 +23,7 @@ class PaginatedList extends React.Component<Props> {
@observable isLoaded: boolean = false;
@observable isFetchingMore: boolean = false;
@observable isFetching: boolean = false;
@observable renderCount: number = DEFAULT_PAGINATION_LIMIT;
@observable offset: number = 0;
@observable allowLoadMore: boolean = true;
@@ -30,6 +32,12 @@ class PaginatedList extends React.Component<Props> {
this.fetchResults();
}
componentDidUpdate(prevProps: Props) {
if (prevProps.fetch !== this.props.fetch) {
this.fetchResults();
}
}
fetchResults = async () => {
if (!this.props.fetch) return;
@@ -48,6 +56,7 @@ class PaginatedList extends React.Component<Props> {
this.offset += limit;
}
this.renderCount += limit;
this.isLoaded = true;
this.isFetching = false;
this.isFetchingMore = false;
@@ -55,34 +64,47 @@ class PaginatedList extends React.Component<Props> {
@action
loadMoreResults = async () => {
// Don't paginate if there aren't more results or were in the middle of fetching
// Don't paginate if there aren't more results or were currently fetching
if (!this.allowLoadMore || this.isFetching) return;
this.isFetchingMore = true;
await this.fetchResults();
// If there are already cached results that we haven't yet rendered because
// of lazy rendering then show another page.
const leftToRender = this.props.items.length - this.renderCount;
if (leftToRender > 1) {
this.renderCount += DEFAULT_PAGINATION_LIMIT;
}
// If there are less than a pages results in the cache go ahead and fetch
// another page from the server
if (leftToRender <= DEFAULT_PAGINATION_LIMIT) {
this.isFetchingMore = true;
await this.fetchResults();
}
};
render() {
const { items, empty } = this.props;
const { items, heading, empty } = this.props;
const showLoading =
this.isFetching && !this.isFetchingMore && !this.isInitiallyLoaded;
const showEmpty = !items.length || showLoading;
const showList = (this.isLoaded || this.isInitiallyLoaded) && !showLoading;
const showEmpty = !items.length && !showLoading;
const showList =
(this.isLoaded || this.isInitiallyLoaded) && !showLoading && !showEmpty;
return (
<React.Fragment>
{showEmpty && empty}
{showList && (
<React.Fragment>
{heading}
<ArrowKeyNavigation
mode={ArrowKeyNavigation.mode.VERTICAL}
defaultActiveChildIndex={0}
>
{items.map(this.props.renderItem)}
{items.slice(0, this.renderCount).map(this.props.renderItem)}
</ArrowKeyNavigation>
{this.allowLoadMore && (
<Waypoint key={this.offset} onEnter={this.loadMoreResults} />
<Waypoint key={this.renderCount} onEnter={this.loadMoreResults} />
)}
</React.Fragment>
)}