UNPKG

ahooks

Version:
145 lines (144 loc) 5.75 kB
import { __awaiter } from "tslib"; import { useMemo, useRef, useState } from 'react'; import useEventListener from '../useEventListener'; import useMemoizedFn from '../useMemoizedFn'; import useRequest from '../useRequest'; import useUpdateEffect from '../useUpdateEffect'; import { getTargetElement } from '../utils/domTarget'; import { getClientHeight, getScrollHeight, getScrollTop } from '../utils/rect'; const useInfiniteScroll = (service, options = {}) => { const { target, isNoMore, threshold = 100, direction = 'bottom', reloadDeps = [], manual, onBefore, onSuccess, onError, onFinally, } = options; const [finalData, setFinalData] = useState(); const [loadingMore, setLoadingMore] = useState(false); const isScrollToTop = direction === 'top'; // lastScrollTop is used to determine whether the scroll direction is up or down const lastScrollTop = useRef(undefined); // scrollBottom is used to record the distance from the bottom of the scroll bar const scrollBottom = useRef(0); // flag: set in onSuccess (bottom direction only) to trigger scrollMethod via effect const pendingBottomScrollCheckRef = useRef(false); const noMore = useMemo(() => { if (!isNoMore) { return false; } return isNoMore(finalData); }, [finalData]); const { loading, error, run, runAsync, cancel } = useRequest((lastData) => __awaiter(void 0, void 0, void 0, function* () { const currentData = yield service(lastData); return { currentData, lastData }; }), { manual, onFinally: (_, d, e) => { setLoadingMore(false); onFinally === null || onFinally === void 0 ? void 0 : onFinally(d === null || d === void 0 ? void 0 : d.currentData, e); }, onBefore: () => onBefore === null || onBefore === void 0 ? void 0 : onBefore(), onSuccess: (d) => { var _a, _b, _c; if (!d.lastData) { setFinalData(Object.assign(Object.assign({}, d.currentData), { list: [...((_a = d.currentData.list) !== null && _a !== void 0 ? _a : [])] })); } else { setFinalData(Object.assign(Object.assign({}, d.currentData), { list: isScrollToTop ? [...d.currentData.list, ...((_b = d.lastData.list) !== null && _b !== void 0 ? _b : [])] : [...((_c = d.lastData.list) !== null && _c !== void 0 ? _c : []), ...d.currentData.list] })); } if (isScrollToTop) { setTimeout(() => { // use requestAnimationFrame to ensure the scroll position is updated (To ensure compatibility react 19) requestAnimationFrame(() => { let el = getTargetElement(target); el = el === document ? document.documentElement : el; if (el) { const scrollHeight = getScrollHeight(el); el.scrollTo(0, scrollHeight - scrollBottom.current); } }); }); } else { pendingBottomScrollCheckRef.current = true; } onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess(d.currentData); }, onError: (e) => onError === null || onError === void 0 ? void 0 : onError(e), }); const loadMore = useMemoizedFn(() => { if (noMore) { return; } setLoadingMore(true); run(finalData); }); const runAsyncForCurrent = (data) => __awaiter(void 0, void 0, void 0, function* () { const res = yield runAsync(data); return res.currentData; }); const loadMoreAsync = useMemoizedFn(() => { if (noMore) { return Promise.reject(); } setLoadingMore(true); return runAsyncForCurrent(finalData); }); const reload = () => { setLoadingMore(false); return run(); }; const reloadAsync = () => { setLoadingMore(false); return runAsyncForCurrent(); }; const scrollMethod = () => { const el = getTargetElement(target); if (!el) { return; } const targetEl = el === document ? document.documentElement : el; const scrollTop = getScrollTop(targetEl); const scrollHeight = getScrollHeight(targetEl); const clientHeight = getClientHeight(targetEl); if (isScrollToTop) { if (lastScrollTop.current !== undefined && lastScrollTop.current > scrollTop && scrollTop <= threshold) { loadMore(); } lastScrollTop.current = scrollTop; scrollBottom.current = scrollHeight - scrollTop; } else if (scrollHeight - scrollTop <= clientHeight + threshold) { loadMore(); } }; useUpdateEffect(() => { if (!pendingBottomScrollCheckRef.current) { return; } pendingBottomScrollCheckRef.current = false; scrollMethod(); }, [finalData]); useEventListener('scroll', () => { if (loading || loadingMore) { return; } scrollMethod(); }, { target }); useUpdateEffect(() => { run(); }, [...reloadDeps]); return { data: finalData, loading: !loadingMore && loading, error, loadingMore, noMore, loadMore, loadMoreAsync, reload: useMemoizedFn(reload), reloadAsync: useMemoizedFn(reloadAsync), mutate: setFinalData, cancel, }; }; export default useInfiniteScroll;