Skip to content

Instantly share code, notes, and snippets.

@tronghieu60s
Created August 31, 2022 06:24
Show Gist options
  • Save tronghieu60s/940491d37404dae83c4b4d214269c01d to your computer and use it in GitHub Desktop.
Save tronghieu60s/940491d37404dae83c4b4d214269c01d to your computer and use it in GitHub Desktop.

Revisions

  1. tronghieu60s created this gist Aug 31, 2022.
    88 changes: 88 additions & 0 deletions IntersectionObserver.tsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,88 @@
    import React, { useCallback, useEffect, useRef, useState } from 'react';

    type IOProps = {
    ref: React.RefObject<any>;
    };

    type Props = {
    skeleton?: React.ReactNode;
    skeletonDuration?: number;
    options?: {
    root: IntersectionObserver['root'];
    rootMargin: IntersectionObserver['rootMargin'];
    thresholds: IntersectionObserver['thresholds'];
    };
    continueObserving?: boolean;
    onIntersection?: (entries: IntersectionObserverEntry[]) => void;
    children: (fields: IOProps) => React.ReactNode;
    };

    const defaultOptions = {
    root: null,
    rootMargin: '0px',
    thresholds: 0,
    };

    export default function IntersectionObserverInView(props: Props): React.ReactElement {
    const {
    skeleton,
    skeletonDuration = 3000,
    options = defaultOptions,
    continueObserving,
    onIntersection,
    children,
    } = props;

    const containerRef = useRef<HTMLDivElement>(null);
    const [isShow, setIsShow] = useState(false);
    const [hasIntersected, setHasIntersected] = useState(false);

    const onIntersectionCallback = useCallback(
    (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
    if (!continueObserving && !hasIntersected) {
    const entry = entries.find((e) => e.target === containerRef.current);

    if (entry && entry.isIntersecting) {
    setHasIntersected(true);
    setTimeout(() => setIsShow(true), skeleton ? skeletonDuration : 0);
    if (onIntersection) onIntersection(entries);
    if (containerRef.current) observer.unobserve(containerRef.current);
    }
    } else if (continueObserving && onIntersection) {
    onIntersection(entries);
    }
    },
    [continueObserving, hasIntersected, onIntersection, skeleton, skeletonDuration],
    );

    useEffect(() => {
    let observerRefValue: Element | null = null;
    const observer = new IntersectionObserver(onIntersectionCallback, options);

    if (containerRef.current) {
    observer.observe(containerRef.current);
    observerRefValue = containerRef.current;
    }

    return () => {
    if (observerRefValue) observer.unobserve(observerRefValue);
    observer.disconnect();
    };
    }, [hasIntersected, containerRef, onIntersectionCallback, options]);

    const childrenWithProps = children({ ref: containerRef }) as React.ReactElement;

    if (continueObserving) {
    return childrenWithProps;
    }

    if (isShow && hasIntersected) {
    return childrenWithProps;
    }

    if (hasIntersected) {
    return skeleton as React.ReactElement;
    }

    return <div ref={containerRef} />;
    }