Press n or j to go to the next uncovered block, b, p or k for the previous block.
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | 24x 24x 24x | /* * Copyright 2022 Harness Inc. All rights reserved. * Use of this source code is governed by the PolyForm Shield 1.0.0 license * that can be found in the licenses directory at the root of this repository, also available at * https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt. */ import { MutableRefObject, useCallback, useEffect, useRef, useState } from 'react' const DEFAULT_PAGE_SIZE = 10 interface GetItemsWithOffset { offset?: number limit?: number } interface InfiniteScrollProps { // getItems is a function promise that will resolve to an array of items to be displayed getItems: (options: GetItemsWithOffset) => Promise<any> /* ref of the element which when visible in the DOM shall trigger the next call to bring the next set of data. Check the usage in TemplateActivityLog */ loadMoreRef: MutableRefObject<Element | null> // limit prop for defining the size of pagination, default is DEFAULT_PAGE_SIZE limit?: number } interface InfiniteScrollReturnProps { fetching: boolean error: string items: any hasMore: MutableRefObject<boolean> offsetToFetch: MutableRefObject<number> loadItems: () => void attachRefToLastElement: (index: number) => boolean } /* Hook for infinite scroll It will only work with NG APIs that support pagination and resolve with this format { content: [], empty: false, pageIndex: 1, pageItemCount: 3, pageSize: 10, totalItems: 13, totalPages: 2 } What will this hook manage? - Items that are visible on the view. Can be used with any list of items that are resulted from paginated API calls loadItems - Fetch data based on a given offset and limit - Function that accepts a page number/offset variable. It will call the underlying API function with the offset variable */ export const useInfiniteScroll = (props: InfiniteScrollProps): InfiniteScrollReturnProps => { const { getItems, limit = DEFAULT_PAGE_SIZE } = props const [items, setItems] = useState<any>([]) const [fetching, setFetching] = useState(false) const [error, setError] = useState('') const hasMore = useRef(false) const initialPageLoaded = useRef(false) const offsetToFetch = useRef(0) const loadItems = () => { setFetching(true) getItems({ offset: offsetToFetch.current, limit }) .then(response => { setFetching(false) // If the cuurent fetch count exceeds totalItems, set hasMore as false const canFetchMore = response.data.totalItems > response.data.pageIndex * response.data.pageSize + response.data.pageItemCount hasMore.current = canFetchMore const responseContent = response.data.content setItems((prevItems: any) => [...prevItems, ...responseContent]) setError('') }) .catch(err => { setFetching(false) setError(err) setItems([]) }) } /* returns true if index is the last element of the list this is done so that the last element can be noticed by the IntersectionObserver */ const attachRefToLastElement = useCallback( (index: number) => { return index === items.length - 1 && hasMore.current && !fetching }, [fetching, hasMore.current] ) /* call loadItems() and fetch the next batch of data if the target is now visible in viewport hasMore is true fetching is false Example - the last element of the list. We identify the last element by applying ref in the above callback */ const loadMoreCallback = useCallback( entries => { const target = entries[0] if (target.isIntersecting && hasMore.current && !fetching) { offsetToFetch.current += 1 loadItems() } }, [fetching, hasMore.current, loadItems] ) // Set the intersection observer useEffect(() => { // Default options for the IO // 0.25 threshold means call the callback function when 25% of the target is visible in the DOM const options = { threshold: 0.25 } const observer = new IntersectionObserver(loadMoreCallback, options) // loadMoreRef is the target element received from props if (props.loadMoreRef && props.loadMoreRef.current) { observer.observe(props.loadMoreRef.current) } return () => { props.loadMoreRef?.current && observer.unobserve(props.loadMoreRef?.current) } }, [props.loadMoreRef, loadMoreCallback]) // Just to ensure we don't end up fetching multiple times in the initial load useEffect(() => { if (initialPageLoaded.current) { return } loadItems() initialPageLoaded.current = true }, [loadItems]) return { fetching, error, items, hasMore, loadItems, attachRefToLastElement, offsetToFetch } } |