From 51616956d4f83029e9bcdf18fca08e8df72ad839 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Mon, 16 May 2022 14:42:17 +0800 Subject: [PATCH] ClaimList: Factor out infinite-scroll and batch-resolve handling into an effect. It's also possible to move the use of `useClaimListInfiniteScroll` into `claimList` itself, but opted to keep them separate for now so that `claimList` doesn't end up a bloated file like `claimListDiscover`. --- ui/effects/use-claimList-infinite-scroll.js | 62 +++++++++++++++++++++ ui/page/channelsFollowingManage/view.jsx | 38 +++---------- 2 files changed, 69 insertions(+), 31 deletions(-) create mode 100644 ui/effects/use-claimList-infinite-scroll.js diff --git a/ui/effects/use-claimList-infinite-scroll.js b/ui/effects/use-claimList-infinite-scroll.js new file mode 100644 index 000000000..a20eba9b8 --- /dev/null +++ b/ui/effects/use-claimList-infinite-scroll.js @@ -0,0 +1,62 @@ +// @flow +/** + * Infinite-scroll and batch-resolving handling for `claimList` instances. + * + * By default, the list is "locked" during initial mount to have a stable + * pagination, but can be disabled through the `disableListLock` parameter. + */ + +import React from 'react'; + +export default function useClaimListInfiniteScroll( + allUris: Array, + doResolveUris: (uris: Array, returnCachedClaims: boolean, resolveReposts: boolean) => void, + pageSize: number = 30, + disableListLock: boolean = false +) { + // Lock the full set of uris on mount, so we have a stable list for pagination. + const [uris, setUris] = React.useState([]); + + // Infinite-scroll handling. 'page' is 0-indexed. + const [page, setPage] = React.useState(-1); + const lastPage = Math.max(0, Math.ceil(allUris.length / pageSize) - 1); + const [isLoadingPage, setIsLoadingPage] = React.useState(true); + + async function resolveUris(uris) { + return doResolveUris(uris, true, false); + } + + async function resolveNextPage(uris, currPage, pageSize) { + const nextPage = currPage + 1; + const nextUriBatch = uris.slice(nextPage * pageSize, (nextPage + 1) * pageSize); + return resolveUris(nextUriBatch); + } + + function bumpPage() { + if (page < lastPage) { + setIsLoadingPage(true); + resolveNextPage(uris, page, pageSize).finally(() => { + setIsLoadingPage(false); + setPage(page + 1); + }); + } + } + + // -- Initial mount: lock list and resolve first batch: + React.useEffect(() => { + setUris(allUris); + resolveNextPage(allUris, -1, pageSize).finally(() => { + setIsLoadingPage(false); + setPage(0); + }); + }, []); + + // -- Optional: remove the locking behavior for whatever reason: + React.useEffect(() => { + if (disableListLock) { + setUris(allUris); + } + }, [allUris]); + + return { uris, page, isLoadingPage, bumpPage }; +} diff --git a/ui/page/channelsFollowingManage/view.jsx b/ui/page/channelsFollowingManage/view.jsx index 8273df37b..8f6dc9507 100644 --- a/ui/page/channelsFollowingManage/view.jsx +++ b/ui/page/channelsFollowingManage/view.jsx @@ -10,6 +10,7 @@ import Page from 'component/page'; import Spinner from 'component/spinner'; import * as ICONS from 'constants/icons'; import { SIDEBAR_SUBS_DISPLAYED } from 'constants/subscriptions'; +import useClaimListInfiniteScroll from 'effects/use-claimList-infinite-scroll'; function getFilteredUris(uris, filterQuery) { if (filterQuery) { @@ -34,45 +35,20 @@ type Props = { export default function ChannelsFollowingManage(props: Props) { const { subscribedChannelUris, lastActiveSubs, doResolveUris, doFetchLastActiveSubs } = props; - - // The locked-on-mount full set of subscribed uris. - const [uris, setUris] = React.useState([]); + const { uris, page, isLoadingPage, bumpPage } = useClaimListInfiniteScroll( + subscribedChannelUris, + doResolveUris, + FOLLOW_PAGE_SIZE + ); // Filtered query and their uris. const [filterQuery, setFilterQuery] = React.useState(''); const [filteredUris, setFilteredUris] = React.useState(null); - // Infinite-scroll handling. 'page' is 0-indexed. - const [page, setPage] = React.useState(-1); - const lastPage = Math.max(0, Math.ceil(uris.length / FOLLOW_PAGE_SIZE) - 1); - const [loadingPage, setLoadingPage] = React.useState(false); - async function resolveUris(uris) { return doResolveUris(uris, true, false); } - async function resolveNextPage(uris, currPage, pageSize = FOLLOW_PAGE_SIZE) { - const nextPage = currPage + 1; - const nextUriBatch = uris.slice(nextPage * pageSize, (nextPage + 1) * pageSize); - return resolveUris(nextUriBatch); - } - - function bumpPage() { - if (page < lastPage) { - setLoadingPage(true); - resolveNextPage(uris, page).finally(() => { - setLoadingPage(false); - setPage(page + 1); - }); - } - } - - React.useEffect(() => { - setUris(subscribedChannelUris); - resolveNextPage(subscribedChannelUris, -1).finally(() => setPage(0)); - // eslint-disable-next-line react-hooks/exhaustive-deps, (lock list on mount so it doesn't shift when unfollow) - }, []); - React.useEffect(() => { const filteredUris = getFilteredUris(uris, filterQuery); if (filteredUris) { @@ -144,7 +120,7 @@ export default function ChannelsFollowingManage(props: Props) { onScrollBottom={bumpPage} page={page + 1} pageSize={FOLLOW_PAGE_SIZE} - loading={loadingPage} + loading={isLoadingPage} useLoadingSpinner />