Skip to content

React Compiler: useVirtual results are cached at their initial values #736

Closed

Description

@gdorsi

Describe the bug

An usage of useVirtual gets compiled by React Compiler into something like this:

const virtualizer = (0,
_tanstack_react_virtual__WEBPACK_IMPORTED_MODULE_9__.useVirtualizer)(t3);
let t4;
if ($[9] !== virtualizer) {
  t4 = virtualizer.getVirtualItems();
  $[9] = virtualizer;
  $[10] = t4;
} else {
  t4 = $[10];
}

Since the virtualizer ref never changes, the virtual items never get updated.

Not sure if this should be a Tanstack Virtual issue or React Compiler needs to apply less assumptions when caching values, but thought that would be good to flag the problem here.

Wrapping useVirtual into something like this fixes the issue:

const useVirtualWrapper = (...args) => {
  'use no memo';
  return { ...useVirtual(...args) };
}

Your minimal, reproducible example

https://codesandbox.io/p/devbox/runtime-dew-9rlvm7

Steps to reproduce

  1. Open the example (forked from https://tanstack.com/virtual/latest/docs/framework/react/examples/fixed)
  2. The virtual lists are rendering no items

Expected behavior

The UI should be the same as https://tanstack.com/virtual/latest/docs/framework/react/examples/fixed

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

  • MacOS, Chrome and Firefox

tanstack-virtual version

3.5.0

TypeScript version

No response

Additional context

No response

Terms & Code of Conduct

  • I agree to follow this project's Code of Conduct
  • I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.

Activity

  1. changed the title [-]React Compiler Compat: useVirtual results are cached at their initial values[/-] [+]React Compiler: useVirtual results are cached at their initial values[/+] on May 23, 2024
  2. relsunkaev commented on Jun 15, 2024

    @relsunkaev

    Similar thing happens if the component is wrapped in memo. @tannerlinsley this error only shows up with strict mode off (i.e. in production).

  3. relsunkaev commented on Jun 15, 2024

    @relsunkaev

    Specifying initialRect made it work for me

  4. nicolo-danzi-ok commented on Jul 31, 2024

    @nicolo-danzi-ok

    got same problem, very annoying when using react-compiler. My workaround was to save an instance of virtualizer as state when the onChange function is saved, and use that instance in my component instead of the instance returned by the hook.

        const [virtualizer, setVirtualizer] = useState<Virtualizer<HTMLDivElement, Element> | null>(null);
    
        useVirtualizer({
            count: data.getCount(),
            estimateSize: () => 30,
            getScrollElement: () => scrollableRef.current,
            onChange: (i) => setVirtualizer(i),
        });
    
       ....
        <>
           virtualizer?.getVirtualItems().map(v => {
              ....
           }
        </>
    

    any official solution or hint?

  5. piecyk commented on Dec 12, 2024

    @piecyk
    Collaborator

    'use no memo' seems like a reasonable temporary workaround! https://react.dev/learn/react-compiler#something-is-not-working-after-compilation

  6. Grawl commented on Dec 19, 2024

    @Grawl

    Specifying initialRect made it work for me

    Can you show how exactly did you get it?

  7. GrahamQuan commented on Mar 9, 2025

    @GrahamQuan
    export default function VirtualList() {
      // opt out of react compiler
      'use no memo';
    }

    this works for me

  8. PaloSP commented on Jul 10, 2025

    @PaloSP

    Any news?

  9. lgrones commented on Jul 12, 2025

    @lgrones

    Here's a solution that works with TS and pretty strict eslint rules (e.g. where spreading classes is forbidden)

    const useVirtual = (options: Parameters<typeof useVirtualizer>[0]) => {
      'use no memo';
    
      const { getVirtualItems, getTotalSize } = useVirtualizer(options);
    
      return { virtualItems: getVirtualItems(), totalSize: getTotalSize(), /*...and whatever else you need*/ };
    };

    But I'd also like to understand how the virtualizer triggers a rerender in the first place if its reference remains stable (which it seems to be according to the compiler output). How does React notice a change if you need to call getVirtualItems during a render to get new items?

    The issue could be fixed by returning 'reactive' things directly as new references instead of returning a function to call, e.g returning virtualItems as an array instead of getVirtualItems as a function. This way the virtualizer can remain stable and you can opt into each feature that should rerender your component by simply destructuring what you need.

    Something like this

     const { virtualItems, totalSize } = useVirtual({
          count: items.length,
          getScrollElement: () => scrollRef.current,
          estimateSize: () => rowHeight
        });
  10. ivan-palatov commented on Jan 14, 2026

    @ivan-palatov

    any news on actually fixing the issue? "use no memo" directive is just a workaround and the related PR seems to have been closed almost a year ago #851

  11. 10 remaining items

  12. piecyk commented on Jun 8, 2026

    @piecyk
    Collaborator

    Btw, @eikowagenknecht, I’d also add overflow-anchor: none to the scrolling container. Sometimes the browser’s scroll anchoring conflicts with virtualization, so it’s better to disable automatic scroll adjustment.

    <div ref={parentRef} style={{ overflow: 'auto', height: 400, overflowAnchor: 'none' }}>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions