InfiniteScroll.tsx 1.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. import React, { Ref, useEffect, useState } from 'react';
  2. import type { SWRInfiniteResponse } from 'swr/infinite';
  3. type Props<T> = {
  4. swr: SWRInfiniteResponse<T>
  5. children: React.ReactChild | ((item: T) => React.ReactNode)
  6. loadingIndicator?: React.ReactNode
  7. endingIndicator?: React.ReactNode
  8. isReachingEnd: boolean | ((swr: SWRInfiniteResponse<T>) => boolean)
  9. offset?: number
  10. }
  11. const useIntersection = <T extends HTMLElement>(): [boolean, Ref<T>] => {
  12. const [intersecting, setIntersecting] = useState<boolean>(false);
  13. const [element, setElement] = useState<HTMLElement>();
  14. useEffect(() => {
  15. if (!element) return;
  16. const observer = new IntersectionObserver((entries) => {
  17. setIntersecting(entries[0]?.isIntersecting);
  18. });
  19. observer.observe(element);
  20. return () => observer.unobserve(element);
  21. }, [element]);
  22. return [intersecting, el => el && setElement(el)];
  23. };
  24. const InfiniteScroll = <T, >(props: Props<T>): React.ReactElement<Props<T>> => {
  25. const {
  26. swr,
  27. swr: { setSize, data, isValidating },
  28. children,
  29. loadingIndicator,
  30. endingIndicator,
  31. isReachingEnd,
  32. offset = 0,
  33. } = props;
  34. const [intersecting, ref] = useIntersection<HTMLDivElement>();
  35. const ending = typeof isReachingEnd === 'function' ? isReachingEnd(swr) : isReachingEnd;
  36. useEffect(() => {
  37. if (intersecting && !isValidating && !ending) {
  38. setSize(size => size + 1);
  39. }
  40. }, [intersecting, isValidating, setSize, ending]);
  41. return (
  42. <>
  43. {typeof children === 'function' ? data?.map(item => children(item)) : children}
  44. <div style={{ position: 'relative' }}>
  45. <div ref={ref} style={{ position: 'absolute', top: offset }}></div>
  46. {ending ? endingIndicator : loadingIndicator}
  47. </div>
  48. </>
  49. );
  50. };
  51. export default InfiniteScroll;