import { ref, onMounted, onUnmounted } from "vue"; import { router } from "@inertiajs/vue3"; /** * Composable for infinite scroll with Inertia v2. * * @param {Function} getProp - () => the current paginator object from Inertia props * @param {string} propName - the prop key name to reload * @param {string} pageParam - query string parameter name for page number * @param {Function} getRouteUrl - () => current URL to reload */ export function useInfiniteList(getProp, propName, pageParam, getRouteUrl) { const items = ref([]); const currentPage = ref(1); const lastPage = ref(1); const isLoadingMore = ref(false); const sentinelRef = ref(null); let observer = null; function syncFromProp() { const prop = getProp(); if (!prop) return; lastPage.value = prop.last_page ?? 1; } function appendFromProp() { const prop = getProp(); if (!prop?.data) return; // append only new items (avoid duplicates by id) const existingIds = new Set(items.value.map((i) => i.id)); const newItems = prop.data.filter((i) => !existingIds.has(i.id)); items.value.push(...newItems); } function reset(initialProp) { items.value = initialProp?.data ?? []; currentPage.value = initialProp?.current_page ?? 1; lastPage.value = initialProp?.last_page ?? 1; } function loadMore() { if (isLoadingMore.value) return; if (currentPage.value >= lastPage.value) return; const nextPage = currentPage.value + 1; isLoadingMore.value = true; const params = new URLSearchParams(window.location.search); params.set(pageParam, nextPage); router.reload({ url: `${window.location.pathname}?${params.toString()}`, only: [propName], preserveScroll: true, preserveState: true, onSuccess: () => { appendFromProp(); currentPage.value = nextPage; isLoadingMore.value = false; }, onError: () => { isLoadingMore.value = false; }, }); } onMounted(() => { observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting) { loadMore(); } }, { rootMargin: "200px" } ); if (sentinelRef.value) { observer.observe(sentinelRef.value); } }); onUnmounted(() => { observer?.disconnect(); }); return { items, currentPage, lastPage, isLoadingMore, sentinelRef, reset, syncFromProp, appendFromProp, loadMore, }; }