UNPKG

codedsaif-react-hooks

Version:

To make it easy for you to get started with GitLab, here's a list of recommended next steps.

125 lines 5.72 kB
import { useState, useEffect, useCallback } from "react"; /** * useInfiniteScroll * Supports both automatic callback fetching and manual slicing with scroll. * @param {Function|null} callback - Called when scroll reaches bottom (set null for manual mode) * @param {Object} options - Configuration options * @param {React.RefObject} [options.containerRef=null] - Scrollable container ref * @param {boolean} [options.manual=false] - If true, enables manual mode and returns 'limit' * @param {number} [options.start=10] - Initial limit in manual mode * @param {number} [options.pace=10] - Increment step for limit * @param {number} [options.offset=100] - Distance from bottom to trigger scroll (in px) * @param {boolean} [options.hasMore=true] - Set false to stop infinite loading when no more data * @param {boolean} [options.debug=false] - Enable funny console logs * * @returns {[boolean, Function]|number} * - If manual=false: returns [isFetching, setIsFetching] * - If manual=true: returns limit (number) * * Example: Auto-fetch Mode (Default) * ------------------------------------- * const containerRef = useRef(null); * const [data, setData] = useState([]); * const [page, setPage] = useState(1); * const [hasMore, setHasMore] = useState(true); * const fetchMore = async () => { * const res = await fetch(`https://medrum.herokuapp.com/feeds/?source=5718e53e7a84fb1901e05971&page=${page}&sort=latest`); * const json = await res.json(); * if (json.length === 0) setHasMore(false); * // OR if(json.count === data.length) setHasMore(false); * else { * setData(prev => [...prev, ...json]); * setPage(prev => prev + 1); * } * }; * const [isFetching, setIsFetching] = useInfiniteScroll(fetchMore, { containerRef, hasMore }); * * Example: Manual Slice Mode * ------------------------------ * const containerRef = useRef(null); * const [data, setData] = useState([]); * const [page, setPage] = useState(1); * const [hasMore, setHasMore] = useState(true); * const limit = useInfiniteScroll(null, { containerRef, manual: true, start: 10, pace: 10, hasMore }); * const fetchMore = async () => { * const res = await fetch(`https://medrum.herokuapp.com/feeds/?source=5718e53e7a84fb1901e05971&page=${page}&sort=latest`); * const json = await res.json(); * if (json.length === 0) setHasMore(false); * else { * setData(prev => [...prev, ...json]); * setPage(prev => prev + 1); * } * }; * useEffect(() => { * fetchMore(); * }, [limit]); * return ( * <ul ref={containerRef} style={{ height: "400px", overflowY: "scroll" }}> * {data.slice(0, limit).map(item => ( * <li key={item.id}>{item.title}</li> * ))} * </ul> * ); */ const useInfiniteScroll = ( callback, { containerRef = null, manual = false, start = 10, pace = 10, offset = 10, hasMore = true, debug = false, } = {} ) => { const [isFetching, setIsFetching] = useState(false); const [limit, setLimit] = useState(start); const handleScroll = useCallback(() => { const el = containerRef?.current || document.documentElement; if (debug) console.log("[🌀 useInfiniteScroll] Scroll event fired..."); const scrollTop = el?.scrollTop || window.scrollY; const scrollHeight = el?.scrollHeight || document.body.scrollHeight; const clientHeight = el?.clientHeight || window.innerHeight; const nearBottom = clientHeight + scrollTop >= scrollHeight - offset; if (debug) { console.log("[🌀 useInfiniteScroll] Scroll event fired..."); console.log( `[🔍 Scroll Check] scrollTop: ${scrollTop}, clientHeight: ${clientHeight}, scrollHeight: ${scrollHeight}, nearBottom: ${nearBottom}` ); } if (nearBottom && !isFetching && hasMore) { if (debug) console.log("[🚀 Trigger] Threshold hit. Loading more..."); if (manual) { setLimit((prev) => { if (debug) console.log("[📦 Manual Mode] Increasing limit..."); return prev + pace; }); } else { setIsFetching(true); } } else { if (debug) { console.log("[❌ Ignored Scroll] Conditions not met:"); console.table({ nearBottom, isFetching, hasMore }); } } }, [containerRef, isFetching, hasMore, manual, offset, pace, debug]); useEffect(() => { const el = containerRef?.current || window; if (debug) console.log("📌 Binding scroll event on", el === window ? "window" : "custom container"); el.addEventListener("scroll", handleScroll); return () => { if (debug) console.log("[🔌 useInfiniteScroll] Detaching scroll listener..."); el.removeEventListener("scroll", handleScroll); }; }, [handleScroll, containerRef, debug]); useEffect(() => { if (!manual && isFetching) { if (debug) console.log("[🔄 useInfiniteScroll] Calling fetch callback..."); const maybePromise = callback?.(); if (maybePromise?.finally) { maybePromise.finally(() => { if (debug) console.log("[✅ useInfiniteScroll] Fetch complete!"); setIsFetching(false); }); } else { setIsFetching(false); } } }, [isFetching, manual, callback, debug]); return manual ? limit : [isFetching, setIsFetching]; }; export default useInfiniteScroll;