简体   繁体   中英

Higher Order Component to observe Visibility: React?

I have created a higher order component as shown below:

import React from 'react';

interface IVisibility {
    Component: JSX.Element;
    visibilityThreshold?: number;
    onVisibleCallback?: () => void;
}

const VisibilityHandler = ({
    Component,
    visibilityThreshold,
    onVisibleCallback
}: IVisibility) => {
    const ref = React.useRef(null);
    React.useEffect(() => {
        const componentObserver = new IntersectionObserver(
            (entries) => {
                const [entry] = entries;
                if (entry.isIntersecting) {
                    onVisibleCallback ? onVisibleCallback() : null;
                }
            },
            {
                rootMargin: '0px',
                threshold: visibilityThreshold ?? 0
            }
        );
        const current = ref.current;

        if (current) componentObserver.observe(current);

        return () => {
            componentObserver.disconnect();
        };
    }, [visibilityThreshold, onVisibleCallback]);

    return <section ref={ref}>{Component}</section>;
};

export default VisibilityHandler;

And use it like this:

<VisibilityHandler Component={<div>Hello World</div>} />

However this wraps every component into a section which I don't want. I tried using React.Fragment but that doesn't let you pass ref to track the component. Is there a better way to re-create this HOC in order to incorporate visibility tracking without wrapping it in additional div or section?

You can use

  • function as a children
  • React.cloneElement

Function as a children

<VisibilityHandler Component={({ ref }) => <div ref={ref}>Hello world</div>} />

You have to change you HOC code

 - return <section ref={ref}>{Component}</section>;
 + return Component({ ref });

React.cloneElement

Documentation

your case

 - return <section ref={ref}>{Component}</section>;
 + return React.cloneElement(Component, { ref });

But I highly recommend use hook (packages) instead of HOC.

I found a really neat way to do so like this:

import React from 'react';

interface IVisibility {
    Component: JSX.Element;
    visibilityThreshold?: number;
    onVisibleCallback?: () => void;
}

const VisibilityHandler = ({
    Component,
    visibilityThreshold,
    onVisibleCallback
}: IVisibility): JSX.Element => {
    const ref = React.useRef(null);
    React.useEffect(() => {
        const componentObserver = new IntersectionObserver(
            (entries) => {
                const [entry] = entries;
                if (entry.isIntersecting) {
                    onVisibleCallback ? onVisibleCallback() : null;
                }
            },
            {
                rootMargin: '0px',
                threshold: visibilityThreshold ?? 0
            }
        );
        const current = ref.current;

        if (current) componentObserver.observe(current);

        return () => {
            componentObserver.disconnect();
        };
    }, [visibilityThreshold, onVisibleCallback]);

    return <Component.type {...Component.props} ref={ref} />;
};

export default VisibilityHandler;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM