Swizec / useDimensions

A React Hook to measure DOM nodes
596 stars 45 forks source link

All dimensions returned as 0 #29

Open dfsa-bot opened 4 years ago

dfsa-bot commented 4 years ago

Hey, I'm kind of stuck with all dimensions being 0 on first Component render. Hot-reloading somehow magically sets the values to correct ones but that obviously won't do. Nothing fancy here:

const [itemRef, { x }, node] = useDimensions()

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

...
return (
  <Component ref={itemRef}>
    {children}
  </Component>
)
...

Actually the first render gets me all 'undefined' (including node), the second one comes back with 0s and node as actual HTMLElement.

Any ideas what might be the cause of this? The container element is a flex-box wrapper - I thought this might be it but then tried to run useDimensions on that element and same results came back

furkankly commented 3 years ago

I don't know the actual reason behind this but as I was trying to debug this problem, I've realized even though the getDimensionObject receives the correct node each time it's called, getBoundingClientRect call in it, returns 0 values for the initial render just like you mentioned.

The measure function const measure = () => window.requestAnimationFrame(() => setDimensions(getDimensionObject(node))); in useLayoutEffect is where we set the dimensions state and I wanted to make sure node state is up to date before calling the getDimensionObject as i mentioned above.

It's not recommended to store the ref (the actual node) in a state as setState (setNode in our case) is async. So what is the proper way to store the actual node in callback refs rather than a state? I don't know, maybe just using a regular ref instantiated by useRef with its .current so we can do nodeRef.current = node in our callback ref instead of doing setNode.

But whole point of using callback refs is to have more control over the refs we create, so we actually make these refs our part of the data flow by using useCallback hook as the regular refs can go deep down and we can't have them as our hook dependencies. (they don't even trigger re-renders as they are mutable.) So using regular refs to store the actual nodes while using callback refs feel so wrong in so many ways and I stick with a state to store them just like in this library.

At this point, I just wanted to make sure I'm getting the dimensions of the "final version" of the node in the initial render so I wanted to give some time to async setState to settle down by putting a timeout on requestAnimationFrame just like:

const measure = () => window.requestAnimationFrame(() => setTimeout(() => setDimensions(getDimensionObject(node)), 100) );

and it worked. (Not to mention about the performance but this addition feels quite hacky...)