Some checks are pending
Deploy Volleyball CMS / deploy (push) Waiting to run
83 lines
3.4 KiB
TypeScript
83 lines
3.4 KiB
TypeScript
/*
|
|
* Copyright 2024 Adobe. All rights reserved.
|
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
* governing permissions and limitations under the License.
|
|
*/
|
|
|
|
import {RefObject, useCallback, useRef} from 'react';
|
|
import {useEvent} from './useEvent';
|
|
|
|
import {useLayoutEffect} from './useLayoutEffect';
|
|
|
|
export interface LoadMoreProps {
|
|
/** Whether data is currently being loaded. */
|
|
isLoading?: boolean,
|
|
/** Handler that is called when more items should be loaded, e.g. while scrolling near the bottom. */
|
|
onLoadMore?: () => void,
|
|
/**
|
|
* The amount of offset from the bottom of your scrollable region that should trigger load more.
|
|
* Uses a percentage value relative to the scroll body's client height. Load more is then triggered
|
|
* when your current scroll position's distance from the bottom of the currently loaded list of items is less than
|
|
* or equal to the provided value. (e.g. 1 = 100% of the scroll region's height).
|
|
* @default 1
|
|
*/
|
|
scrollOffset?: number,
|
|
/** The data currently loaded. */
|
|
items?: any
|
|
}
|
|
|
|
export function useLoadMore(props: LoadMoreProps, ref: RefObject<HTMLElement | null>): void {
|
|
let {isLoading, onLoadMore, scrollOffset = 1, items} = props;
|
|
|
|
// Handle scrolling, and call onLoadMore when nearing the bottom.
|
|
let isLoadingRef = useRef(isLoading);
|
|
let prevProps = useRef(props);
|
|
let onScroll = useCallback(() => {
|
|
if (ref.current && !isLoadingRef.current && onLoadMore) {
|
|
let shouldLoadMore = ref.current.scrollHeight - ref.current.scrollTop - ref.current.clientHeight < ref.current.clientHeight * scrollOffset;
|
|
|
|
if (shouldLoadMore) {
|
|
isLoadingRef.current = true;
|
|
onLoadMore();
|
|
}
|
|
}
|
|
}, [onLoadMore, ref, scrollOffset]);
|
|
|
|
let lastItems = useRef(items);
|
|
useLayoutEffect(() => {
|
|
// Only update isLoadingRef if props object actually changed,
|
|
// not if a local state change occurred.
|
|
if (props !== prevProps.current) {
|
|
isLoadingRef.current = isLoading;
|
|
prevProps.current = props;
|
|
}
|
|
|
|
// TODO: Eventually this hook will move back into RAC during which we will accept the collection as a option to this hook.
|
|
// We will only load more if the collection has changed after the last load to prevent multiple onLoadMore from being called
|
|
// while the data from the last onLoadMore is being processed by RAC collection.
|
|
let shouldLoadMore = ref?.current
|
|
&& !isLoadingRef.current
|
|
&& onLoadMore
|
|
&& (!items || items !== lastItems.current)
|
|
&& ref.current.clientHeight === ref.current.scrollHeight;
|
|
|
|
if (shouldLoadMore) {
|
|
isLoadingRef.current = true;
|
|
onLoadMore?.();
|
|
}
|
|
|
|
lastItems.current = items;
|
|
}, [isLoading, onLoadMore, props, ref, items]);
|
|
|
|
// TODO: maybe this should still just return scroll props?
|
|
// Test against case where the ref isn't defined when this is called
|
|
// Think this was a problem when trying to attach to the scrollable body of the table in OnLoadMoreTableBodyScroll
|
|
useEvent(ref, 'scroll', onScroll);
|
|
}
|