TanStack / virtual

🤖 Headless UI for Virtualizing Large Element Lists in JS/TS, React, Solid, Vue and Svelte
https://tanstack.com/virtual
MIT License
5.25k stars 271 forks source link

useVirtual does not work if parentRef exists inside a popup #263

Closed dantman closed 2 years ago

dantman commented 2 years ago

If you try to use useVirtual with a parentRef inside a container that may not exist during the component's render. The most common use case being popups like menus.

The standard fix for this kind of issue is to use callback refs with useState (const [parentRef, setParentRef] = useState(null);) instead of using a ref. But useVirtual does not allow passing the node directly and only accepts a ref object.

Reproduction: https://codesandbox.io/s/usevirtual-bug-in-popup-ddq6m?file=/src/main.jsx

This is the same as the issue I reported before with a better explanation of why it happens: https://github.com/TanStack/react-virtual/issues/146

The way useVirtual and useRect handles getting the parent node is unreliable and sometimes never actually starts observing the node.

useVirtual requires that parentRef is a RefObject and not a dom node. useRect then uses a layout effect to turn that into state.

As a result useRect will only ever observe an element if the component calling it does a render that triggers a layout effect at the right time.

I was using useVirtual in a setup where the "parent" was a child inside a popup. As a result useRect never actually recieves the ref if I use a normal useRef.

There would be an easy fix of course. If parentRef didn't have to be a Ref object and could instead be the element directly I could trivially use const [parentRef, setParentRef] = useState(null); and swap ref: parentRef for ref: setParentRef and useRect would reliably get the node.

piecyk commented 2 years ago

Yes that is true, it's a common problem if using ref objects, this article explains the issue

https://medium.com/@teh_builder/ref-objects-inside-useeffect-hooks-eb7c15198780

Correct solution would be to use ref callback for setting parentRef, maybe we could include it in 3.0 cc @tannerlinsley

as-zlynn-philipps commented 2 years ago

I tried a callback ref exactly like the Codesandbox above, and it works. Am I missing something here? Is it just the types that need to be updated?

dantman commented 2 years ago

It's more than types. useVirtual uses parentRef.current in the defaultScrollToFn, to get the scroll element, and useRect hackily uses a layout effect that runs once to set a state value to whatever nodeRef.current is at the start.

as-zlynn-philipps commented 2 years ago

Got it, so we're spoofing .current as a workaround until useVirtual accepts a callback ref in v3.

tannerlinsley commented 2 years ago

This should be alleviated in v3 beta as the hook will gracefully handle the scroll element (parent element in v2) entering and exiting. If it's encountered in the new beta, I'd like a new issue to be opened with a new codesandbox repro. Thanks!

apuntovanini commented 1 year ago

@tannerlinsley this seems to be still relevant. I'm using @tanstack/react-virtual along with @tanstack/react-table with debugging enabled: I have a component available both in modal-dialog and in a normal page, table and/or virtual freeze only in modal, even for small list of records, and keeps logging getVisibleCells, getLeftVisibleCells, etc.

I'll try to extract internal components for a repro, just to understand if it's table or virtual related

apuntovanini commented 1 year ago

This is the reproduction I could prepare: https://codesandbox.io/s/optimistic-leavitt-jkg4kb?file=/src/table.tsx

The issue unfortunately is not easily debuggable, codesandbox restarts vite server when modal close, but you can see many logs before that happens that are similar to the ones I see in my environment. It seems that it loops in re-rendering every row many times