-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy pathuseLoadMore.ts
82 lines (72 loc) · 3.45 KB
/
useLoadMore.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/*
* 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';
// eslint-disable-next-line rulesdir/useLayoutEffectRule
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>) {
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]);
// 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);
}