don't add to the bottom if there is nothing at the bottom

This commit is contained in:
Michael Peters 2022-01-23 10:45:33 -06:00
parent a7866df1ff
commit 7e703f84fd
4 changed files with 51 additions and 66 deletions

View File

@ -1,13 +1,13 @@
import React, { Dispatch, FC, ReactNode, SetStateAction, useEffect } from 'react'; import React, { Dispatch, FC, ReactNode, SetStateAction } from 'react';
import { useColumnReverseInfiniteScroll } from '../require/react-helper'; import { useColumnReverseInfiniteScroll } from '../require/react-helper';
import Retry from './retry'; import Retry from './retry';
export interface InfiniteScrollProps { export interface InfiniteScrollProps {
fetchRetryCallable: () => Promise<void>; fetchRetryCallable: () => Promise<void>;
fetchAboveCallable: () => Promise<{ hasMoreAbove: boolean, removedFromBottom: boolean }>; fetchAboveCallable: () => Promise<void>;
fetchBelowCallable: () => Promise<{ hasMoreBelow: boolean, removedFromTop: boolean }>; fetchBelowCallable: () => Promise<void>;
setScrollRatio: Dispatch<SetStateAction<number>>; setScrollRatio: Dispatch<SetStateAction<number>>;
fetchResult: { hasMoreAbove: boolean, hasMoreBelow: boolean } | null; ends: { hasMoreAbove: boolean, hasMoreBelow: boolean } | null;
fetchError: unknown | null; fetchError: unknown | null;
fetchAboveError: unknown | null; fetchAboveError: unknown | null;
fetchBelowError: unknown | null; fetchBelowError: unknown | null;
@ -24,7 +24,7 @@ const InfiniteScroll: FC<InfiniteScrollProps> = (props: InfiniteScrollProps) =>
fetchAboveCallable, fetchAboveCallable,
fetchBelowCallable, fetchBelowCallable,
setScrollRatio, setScrollRatio,
fetchResult, ends,
fetchError, fetchError,
fetchAboveError, fetchAboveError,
fetchBelowError, fetchBelowError,
@ -36,22 +36,16 @@ const InfiniteScroll: FC<InfiniteScrollProps> = (props: InfiniteScrollProps) =>
const [ const [
updateScrollCallable, updateScrollCallable,
onLoadCallable,
fetchAboveRetry, fetchAboveRetry,
fetchBelowRetry fetchBelowRetry
] = useColumnReverseInfiniteScroll( ] = useColumnReverseInfiniteScroll(
600, 600,
ends,
fetchAboveCallable, fetchAboveCallable,
fetchBelowCallable, fetchBelowCallable,
setScrollRatio setScrollRatio
); );
useEffect(() => {
if (fetchResult) {
onLoadCallable(fetchResult)
}
}, [ fetchResult, onLoadCallable ]);
return ( return (
<div className="infinite-scroll-scroll-base" onScroll={updateScrollCallable}> <div className="infinite-scroll-scroll-base" onScroll={updateScrollCallable}>
<div className="infinite-scroll-elements"> <div className="infinite-scroll-elements">

View File

@ -20,7 +20,7 @@ const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
fetchAboveCallable, fetchAboveCallable,
fetchBelowCallable, fetchBelowCallable,
setScrollRatio, setScrollRatio,
fetchResult, ends,
messagesResult, messagesResult,
messagesFetchError, messagesFetchError,
messagesFetchAboveError, messagesFetchAboveError,
@ -46,7 +46,7 @@ const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
fetchAboveCallable={fetchAboveCallable} fetchAboveCallable={fetchAboveCallable}
fetchBelowCallable={fetchBelowCallable} fetchBelowCallable={fetchBelowCallable}
setScrollRatio={setScrollRatio} setScrollRatio={setScrollRatio}
fetchResult={fetchResult} ends={ends}
fetchError={messagesFetchError} fetchError={messagesFetchError}
fetchAboveError={messagesFetchAboveError} fetchAboveError={messagesFetchAboveError}
fetchBelowError={messagesFetchBelowError} fetchBelowError={messagesFetchBelowError}

View File

@ -344,10 +344,10 @@ function useMultipleGuildSubscriptionScrolling<
fetchBelowFunc: ((reference: T) => Promise<T[] | null>), fetchBelowFunc: ((reference: T) => Promise<T[] | null>),
): [ ): [
fetchRetryCallable: () => Promise<void>, fetchRetryCallable: () => Promise<void>,
fetchAboveCallable: () => Promise<{ hasMoreAbove: boolean, removedFromBottom: boolean }>, fetchAboveCallable: () => Promise<void>,
fetchBelowCallable: () => Promise<{ hasMoreBelow: boolean, removedFromTop: boolean }>, fetchBelowCallable: () => Promise<void>,
setScrollRatio: Dispatch<SetStateAction<number>>, setScrollRatio: Dispatch<SetStateAction<number>>,
fetchResult: { hasMoreAbove: boolean, hasMoreBelow: boolean } | null, ends: { hasMoreAbove: boolean, hasMoreBelow: boolean } | null,
lastResult: SubscriptionResult<T[]> | null, lastResult: SubscriptionResult<T[]> | null,
fetchError: unknown | null, fetchError: unknown | null,
fetchAboveError: unknown | null, fetchAboveError: unknown | null,
@ -370,7 +370,7 @@ function useMultipleGuildSubscriptionScrolling<
const [ fetchAboveError, setFetchAboveError ] = useState<unknown | null>(null); const [ fetchAboveError, setFetchAboveError ] = useState<unknown | null>(null);
const [ fetchBelowError, setFetchBelowError ] = useState<unknown | null>(null); const [ fetchBelowError, setFetchBelowError ] = useState<unknown | null>(null);
const [ fetchResult, setFetchResult ] = useState<{ hasMoreAbove: boolean, hasMoreBelow: boolean } | null>(null); const [ ends, setEnds ] = useState<{ hasMoreAbove: boolean, hasMoreBelow: boolean } | null>(null);
// Percentage of scroll from top. i.e. 300px from top of 1000px scroll = 0.3 scroll ratio // Percentage of scroll from top. i.e. 300px from top of 1000px scroll = 0.3 scroll ratio
const [ scrollRatio, setScrollRatio ] = useState<number>(0.5); const [ scrollRatio, setScrollRatio ] = useState<number>(0.5);
@ -394,17 +394,17 @@ function useMultipleGuildSubscriptionScrolling<
return elements.slice(fromTop, elements.length - fromBottom); return elements.slice(fromTop, elements.length - fromBottom);
} }
const fetchAboveCallable = useCallback(async (): Promise<{ hasMoreAbove: boolean, removedFromBottom: boolean }> => { const fetchAboveCallable = useCallback(async (): Promise<void> => {
if (!isMounted.current) return { hasMoreAbove: false, removedFromBottom: false }; if (!isMounted.current) return;
if (!lastResult || !lastResult.value || lastResult.value.length === 0) return { hasMoreAbove: false, removedFromBottom: false }; if (!lastResult || !lastResult.value || lastResult.value.length === 0) return;
if (guild !== lastResult.guild) return { hasMoreAbove: false, removedFromBottom: false }; if (guild !== lastResult.guild) return;
try { try {
const reference = lastResult.value[0] as T; const reference = lastResult.value[0] as T;
const aboveElements = await fetchAboveFunc(reference); const aboveElements = await fetchAboveFunc(reference);
const lastResultAfterAwait = getStateAfterAwait(setLastResult); const lastResultAfterAwait = getStateAfterAwait(setLastResult);
if (!isMounted.current) return { hasMoreAbove: false, removedFromBottom: false }; if (!isMounted.current) return;
if (!lastResultAfterAwait || guild !== lastResultAfterAwait.guild) return { hasMoreAbove: false, removedFromBottom: false }; if (!lastResultAfterAwait || guild !== lastResultAfterAwait.guild) return;
setFetchAboveError(null); setFetchAboveError(null);
if (aboveElements) { if (aboveElements) {
const hasMoreAbove = aboveElements.length >= maxFetchElements; const hasMoreAbove = aboveElements.length >= maxFetchElements;
@ -418,30 +418,30 @@ function useMultipleGuildSubscriptionScrolling<
} }
return { value: newValue, guild: lastResult.guild }; return { value: newValue, guild: lastResult.guild };
}); });
return { hasMoreAbove, removedFromBottom }; setEnds(prev => (prev && { hasMoreBelow: removedFromBottom || prev.hasMoreBelow, hasMoreAbove: prev.hasMoreAbove }));
} else { } else {
return { hasMoreAbove: false, removedFromBottom: false }; setEnds(prev => (prev && { hasMoreBelow: prev.hasMoreBelow, hasMoreAbove: false }));
} }
} catch (e: unknown) { } catch (e: unknown) {
LOG.error('error fetching above for subscription', e); LOG.error('error fetching above for subscription', e);
const lastResultAfterAwait = getStateAfterAwait(setLastResult); const lastResultAfterAwait = getStateAfterAwait(setLastResult);
if (!isMounted.current) return { hasMoreAbove: false, removedFromBottom: false }; if (!isMounted.current) return;
if (!lastResultAfterAwait || guild !== lastResultAfterAwait.guild) return { hasMoreAbove: false, removedFromBottom: false }; if (!lastResultAfterAwait || guild !== lastResultAfterAwait.guild) return;
setFetchAboveError(e); setFetchAboveError(e);
return { hasMoreAbove: true, removedFromBottom: false }; setEnds(prev => (prev && { hasMoreBelow: prev.hasMoreBelow, hasMoreAbove: true }))
} }
}, [ guild, lastResult, fetchAboveFunc, maxFetchElements ]); }, [ guild, lastResult, fetchAboveFunc, maxFetchElements ]);
const fetchBelowCallable = useCallback(async (): Promise<{ hasMoreBelow: boolean, removedFromTop: boolean }> => { const fetchBelowCallable = useCallback(async (): Promise<void> => {
if (!isMounted.current) return { hasMoreBelow: false, removedFromTop: false }; if (!isMounted.current) return;
if (!lastResult || !lastResult.value || lastResult.value.length === 0) return { hasMoreBelow: false, removedFromTop: false }; if (!lastResult || !lastResult.value || lastResult.value.length === 0) return;
if (guild !== lastResult.guild) return { hasMoreBelow: false, removedFromTop: false }; if (guild !== lastResult.guild) return;
try { try {
const reference = lastResult.value[lastResult.value.length - 1] as T; const reference = lastResult.value[lastResult.value.length - 1] as T;
const belowElements = await fetchBelowFunc(reference); const belowElements = await fetchBelowFunc(reference);
const lastResultAfterAwait = getStateAfterAwait(setLastResult); const lastResultAfterAwait = getStateAfterAwait(setLastResult);
if (!isMounted.current) return { hasMoreBelow: false, removedFromTop: false }; if (!isMounted.current) return;
if (!lastResultAfterAwait || guild !== lastResultAfterAwait.guild) return { hasMoreBelow: false, removedFromTop: false }; if (!lastResultAfterAwait || guild !== lastResultAfterAwait.guild) return;
setFetchBelowError(null); setFetchBelowError(null);
if (belowElements) { if (belowElements) {
const hasMoreBelow = belowElements.length >= maxFetchElements; const hasMoreBelow = belowElements.length >= maxFetchElements;
@ -454,18 +454,18 @@ function useMultipleGuildSubscriptionScrolling<
removedFromTop = true; removedFromTop = true;
} }
return { value: newValue, guild: lastResult.guild }; return { value: newValue, guild: lastResult.guild };
}) });
return { hasMoreBelow, removedFromTop }; setEnds(prev => (prev && { hasMoreBelow, hasMoreAbove: removedFromTop || prev.hasMoreAbove })); // :)
} else { } else {
return { hasMoreBelow: false, removedFromTop: false }; setEnds(prev => (prev && { hasMoreBelow: false, hasMoreAbove: prev.hasMoreAbove }))
} }
} catch (e: unknown) { } catch (e: unknown) {
LOG.error('error fetching below for subscription', e); LOG.error('error fetching below for subscription', e);
const lastResultAfterAwait = getStateAfterAwait(setLastResult); const lastResultAfterAwait = getStateAfterAwait(setLastResult);
if (!isMounted.current) return { hasMoreBelow: false, removedFromTop: false }; if (!isMounted.current) return;
if (!lastResultAfterAwait || guild !== lastResultAfterAwait.guild) return { hasMoreBelow: false, removedFromTop: false }; if (!lastResultAfterAwait || guild !== lastResultAfterAwait.guild) return;
setFetchBelowError(e); setFetchBelowError(e);
return { hasMoreBelow: true, removedFromTop: false }; setEnds(prev => (prev && { hasMoreBelow: true, hasMoreAbove: prev.hasMoreAbove }));
} }
}, [ lastResult, fetchBelowFunc, maxFetchElements ]); }, [ lastResult, fetchBelowFunc, maxFetchElements ]);
@ -476,7 +476,7 @@ function useMultipleGuildSubscriptionScrolling<
fetchValue = fetchValue.slice(Math.max(fetchValue.length - maxElements)).sort(sortFunc); fetchValue = fetchValue.slice(Math.max(fetchValue.length - maxElements)).sort(sortFunc);
} }
//LOG.debug('Got items: ', { fetchValueLength: fetchValue?.length ?? '<empty>' }) //LOG.debug('Got items: ', { fetchValueLength: fetchValue?.length ?? '<empty>' })
setFetchResult({ hasMoreAbove, hasMoreBelow: false }); setEnds({ hasMoreAbove, hasMoreBelow: false });
setLastResult({ value: fetchValue, guild: fetchValueGuild }); setLastResult({ value: fetchValue, guild: fetchValueGuild });
setFetchError(null); setFetchError(null);
}, [ sortFunc, maxFetchElements, maxElements ]); }, [ sortFunc, maxFetchElements, maxElements ]);
@ -494,6 +494,7 @@ function useMultipleGuildSubscriptionScrolling<
const newElements = newEventArgsMap(...args); const newElements = newEventArgsMap(...args);
setLastResult((lastResult) => { setLastResult((lastResult) => {
if (lastResult === null) return null; if (lastResult === null) return null;
if (ends?.hasMoreBelow) return lastResult; // Don't add to bottom if we are not at the bottom
let newValue = (lastResult.value ?? []).concat(newElements).sort(sortFunc); let newValue = (lastResult.value ?? []).concat(newElements).sort(sortFunc);
if (newValue.length > maxElements) { if (newValue.length > maxElements) {
// Remove in a way that tries to keep the scrollbar position consistent // Remove in a way that tries to keep the scrollbar position consistent
@ -566,7 +567,7 @@ function useMultipleGuildSubscriptionScrolling<
fetchAboveCallable, fetchAboveCallable,
fetchBelowCallable, fetchBelowCallable,
setScrollRatio, setScrollRatio,
fetchResult, ends,
lastResult, lastResult,
fetchError, fetchError,
fetchAboveError, fetchAboveError,

View File

@ -252,12 +252,12 @@ export function useAsyncSubmitButton<ResultType>(
export function useColumnReverseInfiniteScroll( export function useColumnReverseInfiniteScroll(
threshold: number, threshold: number,
loadMoreAbove: () => Promise<{ hasMoreAbove: boolean, removedFromBottom: boolean }>, ends: { hasMoreBelow: boolean, hasMoreAbove: boolean } | null,
loadMoreBelow: () => Promise<{ hasMoreBelow: boolean, removedFromTop: boolean }>, loadMoreAbove: () => Promise<void>,
loadMoreBelow: () => Promise<void>,
setScrollRatio: Dispatch<SetStateAction<number>> setScrollRatio: Dispatch<SetStateAction<number>>
): [ ): [
updateCallable: (event: UIEvent<HTMLElement>) => void, updateCallable: (event: UIEvent<HTMLElement>) => void,
onLoadCallable: (params: { hasMoreAbove: boolean, hasMoreBelow: boolean }) => void,
loadAboveRetry: () => Promise<void>, loadAboveRetry: () => Promise<void>,
loadBelowRetry: () => Promise<void> loadBelowRetry: () => Promise<void>
] { ] {
@ -266,28 +266,23 @@ export function useColumnReverseInfiniteScroll(
const [ loadingAbove, setLoadingAbove ] = useState<boolean>(false); const [ loadingAbove, setLoadingAbove ] = useState<boolean>(false);
const [ loadingBelow, setLoadingBelow ] = useState<boolean>(false); const [ loadingBelow, setLoadingBelow ] = useState<boolean>(false);
const [ hasMoreAbove, setHasMoreAbove ] = useState<boolean>(false);
const [ hasMoreBelow, setHasMoreBelow ] = useState<boolean>(false);
const loadAbove = useCallback(async () => { const loadAbove = useCallback(async () => {
if (loadingAbove || !hasMoreAbove) return; if (loadingAbove) return;
if (!ends || !ends.hasMoreAbove) return;
setLoadingAbove(true); setLoadingAbove(true);
const loadResult = await loadMoreAbove(); await loadMoreAbove();
if (!isMounted.current) return; if (!isMounted.current) return;
setHasMoreAbove(loadResult.hasMoreAbove);
setHasMoreBelow(oldHasMoreBelow => oldHasMoreBelow || loadResult.removedFromBottom);
setLoadingAbove(false); setLoadingAbove(false);
}, [ loadingAbove, hasMoreAbove, loadMoreAbove ]); }, [ loadingAbove, loadMoreAbove, ends ]);
const loadBelow = useCallback(async () => { const loadBelow = useCallback(async () => {
if (loadingBelow || !hasMoreBelow) return; if (loadingBelow) return;
if (!ends || !ends.hasMoreBelow) return;
setLoadingBelow(true); setLoadingBelow(true);
const loadResult = await loadMoreBelow(); await loadMoreBelow();
if (!isMounted.current) return; if (!isMounted.current) return;
setHasMoreBelow(loadResult.hasMoreBelow);
setHasMoreAbove(oldHasMoreAbove => oldHasMoreAbove || loadResult.removedFromTop);
setLoadingBelow(false); setLoadingBelow(false);
}, [ loadingBelow, hasMoreBelow, loadMoreBelow ]); }, [ loadingBelow, loadMoreBelow, ends ]);
const onScrollCallable = useCallback(async (event: UIEvent<HTMLElement>) => { const onScrollCallable = useCallback(async (event: UIEvent<HTMLElement>) => {
const scrollTop = event.currentTarget.scrollTop; const scrollTop = event.currentTarget.scrollTop;
@ -318,12 +313,7 @@ export function useColumnReverseInfiniteScroll(
} }
}, [ setScrollRatio, loadAbove, loadBelow, threshold ]); }, [ setScrollRatio, loadAbove, loadBelow, threshold ]);
const onLoadCallable = useCallback((params: { hasMoreAbove: boolean, hasMoreBelow: boolean }) => { return [ onScrollCallable, loadAbove, loadBelow ];
setHasMoreAbove(params.hasMoreAbove);
setHasMoreBelow(params.hasMoreBelow);
}, []);
return [ onScrollCallable, onLoadCallable, loadAbove, loadBelow ];
} }
// Makes sure to also allow you to fly-out a click starting inside of the ref'd element but was dragged outside // Makes sure to also allow you to fly-out a click starting inside of the ref'd element but was dragged outside
@ -485,7 +475,7 @@ export function useContextClickContextMenu(
return createContextMenu(alignment, relativeToPos, close); return createContextMenu(alignment, relativeToPos, close);
}, [ alignment, relativeToPos ]); }, [ alignment, relativeToPos ]);
const [ contextMenu, toggle, close, open ] = useContextMenu(createContextMenuWithRelativeToPos, createContextMenuDeps); const [ contextMenu, _toggle, _close, open ] = useContextMenu(createContextMenuWithRelativeToPos, createContextMenuDeps);
const onContextMenu = useCallback((event: React.MouseEvent<HTMLElement>) => { const onContextMenu = useCallback((event: React.MouseEvent<HTMLElement>) => {
setRelativeToPos({ x: event.clientX, y: event.clientY }); setRelativeToPos({ x: event.clientX, y: event.clientY });