skip to Main Content

I am lazy-loading components on a React page by using react-intersection-observer:

import React from "react";
import { useInView } from "react-intersection-observer";

const LazyComponent = ({ LoadableComponent, ...rest }) => {
  const { ref, inView } = useInView({ rootMargin: "50px 0px" });
  return <div ref={ref}>{inView && <LoadableComponent {...rest} />}</div>;
};

const MyComponent = (props) => {
  return (
    <div style={{ height: "4000px", backgroundColor: "blue" }}>
      <p>This is the top of MyComponent.</p>
    </div>
  );
};

export default function App() {
  return (
    <div className="App">
      <h1>Title</h1>
      <div style={{ height: "1000px", backgroundColor: "green" }}></div>
      <LazyComponent LoadableComponent={MyComponent} />
      <div style={{ height: "1000px", backgroundColor: "red" }}></div>
    </div>
  );
}

You can run the code in this sandbox.

What happens correctly: When I scroll down from the green zone and the lazy-loaded component (in blue) comes in view, it starts loading its code.

The problem is: when I scroll all the way down to the section at the bottom (in red), the lazy-loaded component will unmount. When I scroll back up and the lazy-loaded component comes in view again, it mounts again and the page ‘jumps’ to the top of this component.

How can I prevent this, so that scrolling is smooth and no jumping occurs?

2

Answers


  1. Chosen as BEST ANSWER

    I found a solution already, not sure if it's the best.

    Because react-intersection-observer's useInView sets inView to false as soon as the component is scrolled out of view, this will also unmount LoadableComponent. When it scrolls into view again, the component has to be mounted again. By making inView permanent after it becomes true the first time, we can avoid unmounting of LoadableComponent.

    This may not be the 'cleanest' solution, so please let me know if you have a better solution for this issue.

       const LazyComponent = ({ LoadableComponent, ...rest }) => {
       const { ref, inView } = useInView({ rootMargin: "50px 0px" });
       const [keepInView, setKeepInView] = useState(false);
    
       useEffect(() => {
          if (inView && !keepInView) {
             setKeepInView(true);
          }
       }, [inView, keepInView]);
    
       return <div ref={ref}>{keepInView && <LoadableComponent {...rest} />}</div>;
    };
    

  2. You should probably just fire it once (sniped from your original) so this just returns true after that,

    const { ref, inView } = useInView({ rootMargin: "50px 0px" }, { once: true });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search