maslianok / react-resize-detector

A Cross-Browser, Event-based, Element Resize Detection for React
http://maslianok.github.io/react-resize-detector/
MIT License
1.25k stars 91 forks source link

No measuring on lazy imported component #176

Closed lexanth closed 1 year ago

lexanth commented 3 years ago

https://codesandbox.io/s/charming-lewin-6rnvj?file=/src/App.js

React.lazy automatically forwards refs, so it should be fine to just wrap a component and still get the ref.

However, react-resize-detector doesn't get the delayed ref, because it isn't present by the time the useEffect hook is rerun. ref.current in the useEffect dependencies doesn't do anything because of the way it's mutated without triggering a rerender.

I've seen other implementations which always return callback refs wrapped around a ref object to work around this.

I suspect this would be the case with any component which doesn't attach the ref straight away.

maslianok commented 3 years ago

Hi @lexanth

Thanks for reporting this issue and for providing a code snippet.
Spent some time trying to fix the problem and failed.

The problem is that ref is undefined during the initial render and then it gets silently assigned a value. The library needs to be notified somehow. Usually it happens through rerender.

btw, we have useEffect that is listening for ref.currrent changes, but, for some reason, it doesn't trigger :(

lexanth commented 3 years ago

Yeah the useEffect won't receive the ref.current change because mutating ref.current doesn't trigger a rerender (so doesn't trigger the effect again). Basically the issue is that the ref isn't set by the time the useEffect runs. By the way, the hooks ESLint plugin warns that ref.current in the dependencies array doesn't work.

The solution would be to use a callback ref rather than a mutable ref object - then you can trigger the ResizeObserver creation when the callback is called, rather than depending on the ref's value having been assigned by the time the useEffect runs. But it's a reasonably big change! An alternative approach would be to put the ref pointer in state rather than useRef, then updating it would trigger a rerender.

For anyone else finding this (and for ideas on the fix!), these alternatives don't have the same issue (but have different APIs, other trade-offs etc)

Related react issue: https://github.com/facebook/react/issues/21903

The current built-in canonical solution to this is callback refs, but it’s a bit awkward that callback refs have a different API from effects.

whplh commented 3 years ago

Just checked. Using a "callback ref" proposed in old PR would still solve the issue. Relates to prev issue You can also still see the bug on the intial render for the delayed component in updated demo (~ I'm still not sure sure this will ever work with the tragetRef arg)

whplh commented 3 years ago

@lexanth Which lib did you end up using? I was curious about "use-resize-observer". It seems to support a "targetRef". (But nope =). Merging callbacks (e.g) looks like a good idea if you need to support a "targetRef")

maslianok commented 1 year ago

Closing the issues with no comments in the past year.

To the participants: If you found a workaround or somehow fixed it - let me know. If you ended up using another library - feel free to mention it here