Your component will render before the element is being observed by the Intersection Observer so it's expected that the state would be false on the first render (or first couple renders). This isn't really a problem but there are a couple improvements you could make to get more consistent behavior.

The first is to store the observer object in a ref so that only one is created:

function useIntersectionObserver(options) {
  const [state, setState] = React.useState(false);
  // store the observer in a ref so only one is created
  const observer = React.useRef(new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        console.log("is visible")
      } else {
        console.log("not visible")
  }, options))
  return { observer: observer.current, state };

And the second is to add the observer to your useEffect's dependency array and to return a cleanup function. This will ensure that your useEffect to register the observer is only run when observer changes and that it's properly cleaned up.

function App() {
  const headingRef = React.useRef(null);
  const { observer, state } = useIntersectionObserver({ rootMargin: "10px" });

  React.useEffect(() => {
    if (observer && headingRef.current) {
    // return cleanup function
    return () => {
      if (observer) {
  // correctly specify the observer as a dependency to this side effect
  }, [observer]);

  return (
      <h3 ref={headingRef}>test page</h3>
      <h2>test page</h2>

Full codepen here:

On the codepen open up the console and you'll see that the IntersectionObserver only prints out "is visible" once. Try resizing the rendered content window so that it's no longer visible and you'll see "not visible" printed, etc.

Related Query

More Query from same tag