0

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?

Shivam Sahil
  • 4,055
  • 3
  • 31
  • 62

2 Answers2

1

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.

Kirill Skomarovskiy
  • 1,535
  • 1
  • 7
  • 9
  • Hey thank you very much, this was very helpful. However why do you recommend to use hook? That would add additional dependency right and also increase the production bundle size. Is it just because that would have more optimized implementation? – Shivam Sahil Jan 04 '23 at 10:22
  • Btw I found even neater way to achieve the same. Check out my answer – Shivam Sahil Jan 04 '23 at 10:43
  • Your code increases the production bundle too. If you uses package you shares code with another developers. Packages probably have tests, covers more cases and so on. – Kirill Skomarovskiy Jan 04 '23 at 11:48
0

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;

Shivam Sahil
  • 4,055
  • 3
  • 31
  • 62