Trying to implement react-intersection-observer inside react-grid-layout with no luck

656 views Asked by At

I have a page where there are around 10-15 cards fetching data from APIs and printing graphs. When I load the page they all start fetching data and render a graph which is making page little lagging as obvious.

As a part performance optimization I am trying to implement react-intersection-observer package so only card visible on screen will call APIs and show graph. But that seems to be not working, there is no error so not able to find what's wrong.

If I just comment

<GridLayout
    className="layout"
    layout={layout}
    cols={12}
    rowHeight={100}
    width={800}
>

and

</GridLayout>

Then everything works fine, But not with it

I tried 2-3 different approaches but so far no luck, any help would be appreciated.

Here is the demo

1

There are 1 answers

1
John Li On

It seems that the reason might be Card from antd does not forward ref, while ref is needed for either react-intersection-observer or react-grid-layout to function.

While there is no official guide to keep these libraries work together, perhaps the <InView/> component provided by react-intersection-observer could be a possible approach, since it accepts forwardRef, and can serve as a wrapper component for Card, while it functions as the observer as well.

Potential drawbacks of this approach include <InView /> would be an extra wrapper for Card, which might or might not be a problem depending on the use case.

Forked demo with the experimental approach: codesandbox

Card with the implementation is separated as a custom component to keep the parent cleaner. The onChange property on <InView /> can accept a callback to run what is needed when the component is in view.

A state isInView is defined here just for testing, and I think a custom state would probably be a more suitable approach here than using the render props, which is limited inside <InView /> and also depends on ref to function.

The props forwarded to the custom component are required by react-grid-layout according to their document.

const MyCard = forwardRef(
  (
    {
      style,
      className,
      onMouseDown,
      onMouseUp,
      onTouchEnd,
      children,
      ...props
    },
    ref
  ) => {
    //  For testing only
    const [isInView, setIsInView] = useState(false);

    return (
      <InView
        threshold={0.3}
        //  For testing, accepts callback to run when in view
        onChange={(inView, entry) => setIsInView(inView)}
        ref={ref}
        style={{ ...style }}
        className={className}
        onMouseDown={onMouseDown}
        onMouseUp={onMouseUp}
        onTouchEnd={onTouchEnd}
      >
        <Card
          title="Small size card"
          style={{
            height: "100%",
            border: isInView ? "5px solid hotpink" : "1px solid blue",
          }}
        >
          <h2>{`30% inside viewport: ${isInView}.`}</h2>
          <p>Card content</p>
        </Card>
        {children}
      </InView>
    );
  }
);

Parent component now only include any logic to define layout:

const App = () => {
  const layout = [
    { i: "a", x: 0, y: 0, w: 8, h: 8 },
    { i: "b", x: 0, y: 5, w: 8, h: 8 },
    { i: "c", x: 0, y: 10, w: 8, h: 8 },
    { i: "d", x: 0, y: 10, w: 8, h: 8 },
    { i: "e", x: 0, y: 10, w: 8, h: 8 },
  ];
  return (
    <>
      <GridLayout
        className="layout"
        layout={layout}
        cols={12}
        rowHeight={100}
        width={800}
      >
        {/*  Experimental approach */}
        {layout.map((each) => (
          <MyCard key={each.i || Math.random()} />
        ))}
      </GridLayout>
    </>
  );
};

export default App;

On a side note, because <InView /> is already wired up with the required functionalities, the wrapped Card from antd could be replaced by a custom component with necessary styles, if preferred.