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

View File

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

View File

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

View File

@ -252,12 +252,12 @@ export function useAsyncSubmitButton<ResultType>(
export function useColumnReverseInfiniteScroll(
threshold: number,
loadMoreAbove: () => Promise<{ hasMoreAbove: boolean, removedFromBottom: boolean }>,
loadMoreBelow: () => Promise<{ hasMoreBelow: boolean, removedFromTop: boolean }>,
ends: { hasMoreBelow: boolean, hasMoreAbove: boolean } | null,
loadMoreAbove: () => Promise<void>,
loadMoreBelow: () => Promise<void>,
setScrollRatio: Dispatch<SetStateAction<number>>
): [
updateCallable: (event: UIEvent<HTMLElement>) => void,
onLoadCallable: (params: { hasMoreAbove: boolean, hasMoreBelow: boolean }) => void,
loadAboveRetry: () => Promise<void>,
loadBelowRetry: () => Promise<void>
] {
@ -266,28 +266,23 @@ export function useColumnReverseInfiniteScroll(
const [ loadingAbove, setLoadingAbove ] = 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 () => {
if (loadingAbove || !hasMoreAbove) return;
if (loadingAbove) return;
if (!ends || !ends.hasMoreAbove) return;
setLoadingAbove(true);
const loadResult = await loadMoreAbove();
await loadMoreAbove();
if (!isMounted.current) return;
setHasMoreAbove(loadResult.hasMoreAbove);
setHasMoreBelow(oldHasMoreBelow => oldHasMoreBelow || loadResult.removedFromBottom);
setLoadingAbove(false);
}, [ loadingAbove, hasMoreAbove, loadMoreAbove ]);
}, [ loadingAbove, loadMoreAbove, ends ]);
const loadBelow = useCallback(async () => {
if (loadingBelow || !hasMoreBelow) return;
if (loadingBelow) return;
if (!ends || !ends.hasMoreBelow) return;
setLoadingBelow(true);
const loadResult = await loadMoreBelow();
await loadMoreBelow();
if (!isMounted.current) return;
setHasMoreBelow(loadResult.hasMoreBelow);
setHasMoreAbove(oldHasMoreAbove => oldHasMoreAbove || loadResult.removedFromTop);
setLoadingBelow(false);
}, [ loadingBelow, hasMoreBelow, loadMoreBelow ]);
}, [ loadingBelow, loadMoreBelow, ends ]);
const onScrollCallable = useCallback(async (event: UIEvent<HTMLElement>) => {
const scrollTop = event.currentTarget.scrollTop;
@ -318,12 +313,7 @@ export function useColumnReverseInfiniteScroll(
}
}, [ setScrollRatio, loadAbove, loadBelow, threshold ]);
const onLoadCallable = useCallback((params: { hasMoreAbove: boolean, hasMoreBelow: boolean }) => {
setHasMoreAbove(params.hasMoreAbove);
setHasMoreBelow(params.hasMoreBelow);
}, []);
return [ onScrollCallable, onLoadCallable, loadAbove, loadBelow ];
return [ onScrollCallable, loadAbove, loadBelow ];
}
// 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);
}, [ 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>) => {
setRelativeToPos({ x: event.clientX, y: event.clientY });