From 08725f213248f886c7131d406d263fb42b01d501 Mon Sep 17 00:00:00 2001 From: gtkatakura <8618687+gtkatakura@users.noreply.github.com> Date: Mon, 6 Jul 2026 20:54:22 -0300 Subject: [PATCH] [compiler]: add `useWindowVirtualizer` to known incompatible libraries (#36912) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary #34493 added `@tanstack/react-virtual`'s `useVirtualizer` to the known incompatible libraries, but not `useWindowVirtualizer`. Both hooks are thin wrappers around the same internal `useVirtualizerBase`, so they return the same referentially-stable virtualizer instance. Its methods (e.g. `getVirtualItems()`, `getTotalSize()`) return internally-mutated values rather than new ones, which is exactly the "interior mutability" pattern that breaks memoization described in the module type provider. Because only `useVirtualizer` was registered, code using `useWindowVirtualizer` is silently compiled with the same unsafe memoization. In our app this froze `getVirtualItems()` to its first-render value (an empty `[]` before measurement), producing permanently-empty virtualized lists — the same class of bug #34493 fixed, just via the window variant. This registers `useWindowVirtualizer` alongside `useVirtualizer` with the identical shape, in **both** implementations: - `compiler/packages/babel-plugin-react-compiler/src/HIR/DefaultModuleTypeProvider.ts` - `compiler/crates/react_compiler_hir/src/default_module_type_provider.rs` (Rust port) See also the community report referenced in #34493: https://github.com/TanStack/virtual/issues/736#issuecomment-3065658277 ## How did you test this change? Each addition mirrors the existing `useVirtualizer` entry exactly (same kind, params, return type, and `knownIncompatible` message shape); both are data-only additions to the module type providers, following the precedent set by #34493 which changed only the TS file. The Rust change keeps the two providers in sync. --- .../src/default_module_type_provider.rs | 52 +++++++++++++------ .../src/HIR/DefaultModuleTypeProvider.ts | 11 ++++ 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/compiler/crates/react_compiler_hir/src/default_module_type_provider.rs b/compiler/crates/react_compiler_hir/src/default_module_type_provider.rs index 38668a8bc3e3..448ba5ee816d 100644 --- a/compiler/crates/react_compiler_hir/src/default_module_type_provider.rs +++ b/compiler/crates/react_compiler_hir/src/default_module_type_provider.rs @@ -77,22 +77,42 @@ pub fn default_module_type_provider(module_name: &str) -> Option { })), "@tanstack/react-virtual" => Some(TypeConfig::Object(ObjectTypeConfig { - properties: Some(IndexMap::from_iter([( - "useVirtualizer".to_string(), - TypeConfig::Hook(HookTypeConfig { - positional_params: Some(Vec::new()), - rest_param: Some(Effect::Read), - return_type: Box::new(TypeConfig::TypeReference(TypeReferenceConfig { - name: BuiltInTypeRef::Any, - })), - return_value_kind: None, - no_alias: None, - aliasing: None, - known_incompatible: Some( - "TanStack Virtual's `useVirtualizer()` API returns functions that cannot be memoized safely".to_string(), - ), - }), - )])), + properties: Some(IndexMap::from_iter([ + ( + "useVirtualizer".to_string(), + TypeConfig::Hook(HookTypeConfig { + positional_params: Some(Vec::new()), + rest_param: Some(Effect::Read), + return_type: Box::new(TypeConfig::TypeReference(TypeReferenceConfig { + name: BuiltInTypeRef::Any, + })), + return_value_kind: None, + no_alias: None, + aliasing: None, + known_incompatible: Some( + "TanStack Virtual's `useVirtualizer()` API returns functions that cannot be memoized safely".to_string(), + ), + }), + ), + // `useWindowVirtualizer()` wraps the same virtualizer instance as `useVirtualizer()`, + // so its return value is incompatible for the same reason. + ( + "useWindowVirtualizer".to_string(), + TypeConfig::Hook(HookTypeConfig { + positional_params: Some(Vec::new()), + rest_param: Some(Effect::Read), + return_type: Box::new(TypeConfig::TypeReference(TypeReferenceConfig { + name: BuiltInTypeRef::Any, + })), + return_value_kind: None, + no_alias: None, + aliasing: None, + known_incompatible: Some( + "TanStack Virtual's `useWindowVirtualizer()` API returns functions that cannot be memoized safely".to_string(), + ), + }), + ), + ])), })), _ => None, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/DefaultModuleTypeProvider.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/DefaultModuleTypeProvider.ts index 106fce615c44..0a7a728c1fa2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/DefaultModuleTypeProvider.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/DefaultModuleTypeProvider.ts @@ -101,6 +101,17 @@ export function defaultModuleTypeProvider( returnType: {kind: 'type', name: 'Any'}, knownIncompatible: `TanStack Virtual's \`useVirtualizer()\` API returns functions that cannot be memoized safely`, }, + /* + * `useWindowVirtualizer()` wraps the same virtualizer instance as `useVirtualizer()`, so its return value + * is incompatible for the same reason and we mark the entire hook as incompatible + */ + useWindowVirtualizer: { + kind: 'hook', + positionalParams: [], + restParam: Effect.Read, + returnType: {kind: 'type', name: 'Any'}, + knownIncompatible: `TanStack Virtual's \`useWindowVirtualizer()\` API returns functions that cannot be memoized safely`, + }, }, }; }