BrownBe
BrownBe

Reputation: 987

React Hook who refer to DOM element return "null" on first call

I have got an hook who catch getBoundingClientRect object of a ref DOM element. The problem is, at the first render, it return null and I need to get the value only on first render on my component.

I use it like that in a functional component:

const App = () => {

  // create ref 
  const rootRef = useRef(null);

  // get Client Rect of rootRef 
  const refRect = useBoundingClientRect(rootRef);

  useEffect(()=> {
    // return "null" the first time
    // return "DOMRect" when refRect is update
    console.log(refRect)
  }, [refRect])

  return <div ref={rootRef} >App</div>

}

Here the useBoundingClientRect hook, I call in App Component.

export function useBoundingClientRect(pRef) {

  const getBoundingClientRect = useCallback(() => {
    return pRef && pRef.current && pRef.current.getBoundingClientRect();
  }, [pRef]);

  const [rect, setRect] = useState(null);

  useEffect(() => {
    setRect(getBoundingClientRect());
  },[]);

  return rect;
}

The problem is I would like to cache boundingClientRect object on init and not the second time component is rerender :

  // App Component

  useEffect(()=> {
    // I would like to get boundingClientRect the 1st time useEffect is call.
    console.log(refRect)

  // empty array allow to not re-execute the code in this useEffect
  }, [])

I've check few tutorials and documentations and finds some people use useRef instead of useState hook to keep value. So I tried to use it in my useboundingClientRect hook to catch and return the boundingClientRect value on the first render of my App component. And it works... partially:

export function useBoundingClientRect(pRef) {

  const getBoundingClientRect = useCallback(() => {
    return pRef && pRef.current && pRef.current.getBoundingClientRect();
  }, [pRef]);

  const [rect, setRect] = useState(null);

  // create a new ref
  const rectRef = useRef(null)

  useEffect(() => {
    setRect(getBoundingClientRect());

    // set value in ref
    const rectRef = getBoundingClientRect()

  },[]);

  //  return rectRef for the first time 
  return rect === null ? rectRef : rect;
}

Now the console.log(rectRef) in App Component allow to access the value on first render:

   // App Component

  useEffect(()=> {
    console.log(refRect.current)
  }, [])

But If I try to return refRect.current from useBoundingClientRect hook return null. (What?!)

if anyone can explain theses mistakes to me. Thanks in advance!

Upvotes: 0

Views: 1578

Answers (1)

Shubham Khatri
Shubham Khatri

Reputation: 281774

You need to understand references, mututation, and the asynchronous nature of updates here.

Firstly, when you use state to store the clientRect properties when your custom hook in useEffect runs, it sets value in state which will reflect in the next render cycle since state updates are asynchronous. This is why on first render you see undefined.

Secondly, when you are returning rectRef, you are essentially returning an object in which you later mutate when the useEffect in useBoundingClientRect runs. The data is returned before the useEffect is ran as it runs after the render cycle. Now when useEffect within the component runs, which is after the useEffect within the custom hook runs, the data is already there and has been updated at its reference by the previous useEffect and hence you see the correct data.

Lastly, if you return rectRef.current which is now a immutable value, the custom hook updates the value but at a new reference since the previous one was null and hence you don't see the change in your components useEffect method.

Upvotes: 2

Related Questions