LazyLoader.tsx 1.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import React, { useRef, useState } from 'react';
  2. import { useEffectOnce } from 'react-use';
  3. import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId';
  4. export interface Props {
  5. children: React.ReactNode | (({ isInView }: { isInView: boolean }) => React.ReactNode);
  6. width?: number;
  7. height?: number;
  8. onLoad?: () => void;
  9. onChange?: (isInView: boolean) => void;
  10. }
  11. export function LazyLoader({ children, width, height, onLoad, onChange }: Props) {
  12. const id = useUniqueId();
  13. const [loaded, setLoaded] = useState(false);
  14. const [isInView, setIsInView] = useState(false);
  15. const wrapperRef = useRef<HTMLDivElement>(null);
  16. useEffectOnce(() => {
  17. LazyLoader.addCallback(id, (entry) => {
  18. if (!loaded && entry.isIntersecting) {
  19. setLoaded(true);
  20. onLoad?.();
  21. }
  22. setIsInView(entry.isIntersecting);
  23. onChange?.(entry.isIntersecting);
  24. });
  25. const wrapperEl = wrapperRef.current;
  26. if (wrapperEl) {
  27. LazyLoader.observer.observe(wrapperEl);
  28. }
  29. return () => {
  30. delete LazyLoader.callbacks[id];
  31. wrapperEl && LazyLoader.observer.unobserve(wrapperEl);
  32. if (Object.keys(LazyLoader.callbacks).length === 0) {
  33. LazyLoader.observer.disconnect();
  34. }
  35. };
  36. });
  37. return (
  38. <div id={id} ref={wrapperRef} style={{ width, height }}>
  39. {loaded && (typeof children === 'function' ? children({ isInView }) : children)}
  40. </div>
  41. );
  42. }
  43. LazyLoader.callbacks = {} as Record<string, (e: IntersectionObserverEntry) => void>;
  44. LazyLoader.addCallback = (id: string, c: (e: IntersectionObserverEntry) => void) => (LazyLoader.callbacks[id] = c);
  45. LazyLoader.observer = new IntersectionObserver(
  46. (entries) => {
  47. for (const entry of entries) {
  48. LazyLoader.callbacks[entry.target.id](entry);
  49. }
  50. },
  51. { rootMargin: '100px' }
  52. );