InfiniteScroll.tsx 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import React, {
  2. Ref, useEffect, useState,
  3. } from 'react';
  4. import type { SWRInfiniteResponse } from 'swr/infinite';
  5. type Props<T> = {
  6. swrInifiniteResponse: SWRInfiniteResponse<T>
  7. children: React.ReactNode,
  8. loadingIndicator?: React.ReactNode
  9. endingIndicator?: React.ReactNode
  10. isReachingEnd?: boolean
  11. offset?: number
  12. }
  13. const useIntersection = <E extends HTMLElement>(): [boolean, Ref<E>] => {
  14. const [intersecting, setIntersecting] = useState<boolean>(false);
  15. const [element, setElement] = useState<HTMLElement>();
  16. useEffect(() => {
  17. if (element != null) {
  18. const observer = new IntersectionObserver((entries) => {
  19. setIntersecting(entries[0]?.isIntersecting);
  20. });
  21. observer.observe(element);
  22. return () => observer.unobserve(element);
  23. }
  24. return;
  25. }, [element]);
  26. return [intersecting, el => el && setElement(el)];
  27. };
  28. const LoadingIndicator = (): React.ReactElement => {
  29. return (
  30. <div className="text-muted text-center">
  31. <i className="fa fa-2x fa-spinner fa-pulse me-1"></i>
  32. </div>
  33. );
  34. };
  35. const InfiniteScroll = <E, >(props: Props<E>): React.ReactElement<Props<E>> => {
  36. const {
  37. swrInifiniteResponse: {
  38. setSize, isValidating,
  39. },
  40. children,
  41. loadingIndicator,
  42. endingIndicator,
  43. isReachingEnd,
  44. offset = 0,
  45. } = props;
  46. const [intersecting, ref] = useIntersection<HTMLDivElement>();
  47. useEffect(() => {
  48. if (intersecting && !isValidating && !isReachingEnd) {
  49. setSize(size => size + 1);
  50. }
  51. }, [setSize, intersecting, isValidating, isReachingEnd]);
  52. return (
  53. <>
  54. {children}
  55. <div style={{ position: 'relative' }}>
  56. <div ref={ref} style={{ position: 'absolute', top: offset }}></div>
  57. {isReachingEnd
  58. ? endingIndicator
  59. : loadingIndicator || <LoadingIndicator />
  60. }
  61. </div>
  62. </>
  63. );
  64. };
  65. export default InfiniteScroll;