From 22230c25e5f0e4c1de1f0ca36277e199e4250436 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 13 Jan 2020 18:17:56 -0800 Subject: [PATCH] perf: Use progressive rendering on PaginatedList component (#1156) * Use progressive rendering on PaginatedList component Move drafts and starred views to paginated * heading --- app/components/PaginatedDocumentList.js | 84 ++++--------------------- app/components/PaginatedList.js | 38 ++++++++--- app/scenes/Drafts.js | 29 ++++----- app/scenes/Starred.js | 38 ++++------- 4 files changed, 63 insertions(+), 126 deletions(-) diff --git a/app/components/PaginatedDocumentList.js b/app/components/PaginatedDocumentList.js index 7f59d825e..e856d66a9 100644 --- a/app/components/PaginatedDocumentList.js +++ b/app/components/PaginatedDocumentList.js @@ -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 { - 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 we’re 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 ( - - {showEmpty && empty} - {showList && ( - - {heading} - - {this.allowLoadMore && ( - - )} - + ( + )} - {showLoading && } - + /> ); } } diff --git a/app/components/PaginatedList.js b/app/components/PaginatedList.js index 1b3c25caa..2f4fe8cbb 100644 --- a/app/components/PaginatedList.js +++ b/app/components/PaginatedList.js @@ -11,6 +11,7 @@ import { ListPlaceholder } from 'components/LoadingPlaceholder'; type Props = { fetch?: (options: ?Object) => Promise, options?: Object, + heading?: React.Node, empty?: React.Node, items: any[], renderItem: any => React.Node, @@ -22,6 +23,7 @@ class PaginatedList extends React.Component { @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 { 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 { this.offset += limit; } + this.renderCount += limit; this.isLoaded = true; this.isFetching = false; this.isFetchingMore = false; @@ -55,34 +64,47 @@ class PaginatedList extends React.Component { @action loadMoreResults = async () => { - // Don't paginate if there aren't more results or we’re in the middle of fetching + // Don't paginate if there aren't more results or we’re 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 ( {showEmpty && empty} {showList && ( + {heading} - {items.map(this.props.renderItem)} + {items.slice(0, this.renderCount).map(this.props.renderItem)} {this.allowLoadMore && ( - + )} )} diff --git a/app/scenes/Drafts.js b/app/scenes/Drafts.js index b4aa13947..451d02968 100644 --- a/app/scenes/Drafts.js +++ b/app/scenes/Drafts.js @@ -4,10 +4,9 @@ import { observer, inject } from 'mobx-react'; import Heading from 'components/Heading'; import CenteredContent from 'components/CenteredContent'; -import { ListPlaceholder } from 'components/LoadingPlaceholder'; import Empty from 'components/Empty'; import PageTitle from 'components/PageTitle'; -import DocumentList from 'components/DocumentList'; +import PaginatedDocumentList from 'components/PaginatedDocumentList'; import Subheading from 'components/Subheading'; import InputSearch from 'components/InputSearch'; import NewDocumentMenu from 'menus/NewDocumentMenu'; @@ -20,28 +19,22 @@ type Props = { @observer class Drafts extends React.Component { - componentDidMount() { - this.props.documents.fetchDrafts(); - } - render() { - const { isLoaded, isFetching, drafts } = this.props.documents; - const showLoading = !isLoaded && isFetching; - const showEmpty = isLoaded && !drafts.length; + const { fetchDrafts, drafts } = this.props.documents; return ( Drafts - {showEmpty ? ( - You’ve not got any drafts at the moment. - ) : ( - - Documents - - {showLoading && } - - )} + Documents} + empty={You’ve not got any drafts at the moment.} + fetch={fetchDrafts} + documents={drafts} + showDraft={false} + showCollection + /> + diff --git a/app/scenes/Starred.js b/app/scenes/Starred.js index 566a47c8d..4aaaa6793 100644 --- a/app/scenes/Starred.js +++ b/app/scenes/Starred.js @@ -3,11 +3,10 @@ import * as React from 'react'; import { observer, inject } from 'mobx-react'; import CenteredContent from 'components/CenteredContent'; -import { ListPlaceholder } from 'components/LoadingPlaceholder'; import Empty from 'components/Empty'; import PageTitle from 'components/PageTitle'; import Heading from 'components/Heading'; -import DocumentList from 'components/DocumentList'; +import PaginatedDocumentList from 'components/PaginatedDocumentList'; import InputSearch from 'components/InputSearch'; import Tabs from 'components/Tabs'; import Tab from 'components/Tab'; @@ -22,29 +21,16 @@ type Props = { @observer class Starred extends React.Component { - componentDidMount() { - this.props.documents.fetchStarred(); - } - render() { - const { - isLoaded, - isFetching, - starred, - starredAlphabetical, - } = this.props.documents; + const { fetchStarred, starred, starredAlphabetical } = this.props.documents; const { sort } = this.props.match.params; - const showLoading = !isLoaded && isFetching; - const showEmpty = isLoaded && !starred.length; return ( Starred - {showEmpty ? ( - You’ve not starred any documents yet. - ) : ( - + Recently Updated @@ -53,15 +39,13 @@ class Starred extends React.Component { Alphabetical - - - )} - {showLoading && } + } + empty={You’ve not starred any documents yet.} + fetch={fetchStarred} + documents={sort === 'alphabetical' ? starredAlphabetical : starred} + showCollection + /> +