Reputation: 987
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
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