muller
muller

Reputation: 61

custom hook returns wrong value

I made a custom hook as my intersection observer but I'm getting false and true state at the same time and twice!, first false(twice) and second true(twice) why is this happening? why state changes itself? state must be true because h3 is 100% visible.

here is the code:

App.js

import React from "react";
import "./App.css";
import useIntersectionObserver from "./hooks/useIntersectionObserver";
function App() {
  const headingRef = React.useRef(null);
  const { observer, state } = useIntersectionObserver({ rootMargin: "10px" });
  React.useEffect(() => {
    if (headingRef.current instanceof Element) {
      observer.observe(headingRef.current);
    }
  }, []);
  console.log(state);
  return (
    <>
      <h3 ref={headingRef}>test page</h3>
      <h2>test page</h2>
    </>
  );
}

export default App;

useIntersectionObserver custom hook:

import React from "react";

const useIntersectionObserver = (options) => {
  const [state, setState] = React.useState(false);
  const callback = (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        setState(true);
      } else {
        setState(false);
      }
    });
  };
  const observer = new IntersectionObserver(callback, options);
  return { observer, state };
};

export default useIntersectionObserver;

and here is the weird output: (at initial render) enter image description here

Upvotes: 0

Views: 330

Answers (1)

Spencer Wood
Spencer Wood

Reputation: 654

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")
        setState(true);
      } else {
        console.log("not visible")
        setState(false);
      }
    });
  }, 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) {
      observer.observe(headingRef.current);
    }
    
    // return cleanup function
    return () => {
      if (observer) {
        observer.disconnect();
      }
    }
  // 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: https://codepen.io/spencercwood/pen/mdqegBZ?editors=0011

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.

Upvotes: 1

Related Questions