Skip to content

feat(virtual-core): add useCachedMeasurements option to preserve sizes when list is hidden#1186

Merged
piecyk merged 1 commit into
TanStack:mainfrom
piecyk:damian/feat/use-cached-measurements
Jun 2, 2026
Merged

feat(virtual-core): add useCachedMeasurements option to preserve sizes when list is hidden#1186
piecyk merged 1 commit into
TanStack:mainfrom
piecyk:damian/feat/use-cached-measurements

Conversation

@piecyk
Copy link
Copy Markdown
Collaborator

@piecyk piecyk commented Jun 2, 2026

🎯 Changes

Add useCachedMeasurements option to skip DOM measurement when the list is hidden (e.g. display: none). When enabled, the default measureElement returns the cached size or estimateSize fallback instead of reading the DOM, preventing ResizeObserver from resetting measurements to zero.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features

    • Added useCachedMeasurements option to virtualizer configuration that uses previously cached item sizes instead of DOM measurements.
  • Documentation

    • Updated API documentation describing the new useCachedMeasurements configuration option.
  • Tests

    • Added end-to-end tests verifying cached measurement behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a useCachedMeasurements option to the virtualizer that allows the default measureElement to return cached item sizes instead of reading the DOM. When enabled, measurement logic is preserved when the list is temporarily hidden (e.g., display: none), preventing ResizeObserver callbacks from resetting sizes to zero.

Changes

Cached Measurements Feature

Layer / File(s) Summary
Core implementation
packages/virtual-core/src/index.ts
VirtualizerOptions adds optional useCachedMeasurements?: boolean field defaulting to false. The measureElement function adds a fast path that returns cached sizes from itemSizeCache (with estimateSize fallback) when enabled, bypassing DOM reads.
User-facing documentation
.changeset/use-cached-measurements.md, docs/api/virtualizer.md
Changeset and API docs describe the new option, its interaction with the default measureElement, expected lifecycle toggling, and limitations when custom measureElement is provided.
E2E test infrastructure
packages/react-virtual/e2e/app/vite.config.ts, packages/react-virtual/e2e/app/cached-measurements/index.html
Vite build configuration and HTML entrypoint are added to enable the cached-measurements e2e test app build and module loading.
E2E test app implementation
packages/react-virtual/e2e/app/cached-measurements/main.tsx
React virtualized list demo with 20 variable-height items, toggle button to switch useCachedMeasurements mode, and UI displaying virtualizer's total-size for test assertions.
E2E test validation
packages/react-virtual/e2e/app/test/cached-measurements.spec.ts
Playwright test validates that total-size remains unchanged when the list is hidden, confirming cached measurements prevent ResizeObserver callbacks from resetting sizes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • TanStack/virtual#1183: Both PRs optimize measureElement in packages/virtual-core/src/index.ts to avoid synchronous DOM reads by preferentially returning values from itemSizeCache; this PR builds on that foundational cached-measurement logic.

Poem

🐰 Cache the sizes, skip the DOM,
When lists hide away, measurements stay calm,
No ResizeObserver resets today,
Virtualized items remember their way! 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding a useCachedMeasurements option to preserve item sizes when the list is hidden.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description follows the required template structure with all sections completed: Changes, Checklist items marked, and Release Impact clearly stated.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Jun 2, 2026

View your CI Pipeline Execution ↗ for commit 320f97c

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 2m 36s View ↗
nx run-many --target=build --exclude=examples/** ✅ Succeeded 18s View ↗

☁️ Nx Cloud last updated this comment at 2026-06-02 06:44:42 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 2, 2026

More templates

@tanstack/angular-virtual

npm i https://pkg.pr.new/@tanstack/angular-virtual@1186

@tanstack/lit-virtual

npm i https://pkg.pr.new/@tanstack/lit-virtual@1186

@tanstack/react-virtual

npm i https://pkg.pr.new/@tanstack/react-virtual@1186

@tanstack/solid-virtual

npm i https://pkg.pr.new/@tanstack/solid-virtual@1186

@tanstack/svelte-virtual

npm i https://pkg.pr.new/@tanstack/svelte-virtual@1186

@tanstack/virtual-core

npm i https://pkg.pr.new/@tanstack/virtual-core@1186

@tanstack/vue-virtual

npm i https://pkg.pr.new/@tanstack/vue-virtual@1186

commit: 320f97c

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/react-virtual/e2e/app/test/cached-measurements.spec.ts (1)

10-10: ⚡ Quick win

Replace fixed timeouts with condition-based waits.

The test uses waitForTimeout at three points (200ms, 300ms, 200ms) to wait for rendering and ResizeObserver callbacks. Fixed timeouts make tests fragile—too short and they flake; too long and they slow down the suite.

Consider waiting for specific conditions instead:

// Instead of: await page.waitForTimeout(200)
// Wait for total-size to stabilize
await page.waitForFunction(() => {
  const size = document.querySelector('[data-testid="total-size"]')?.textContent;
  return size && Number(size) > 0;
});

Or use Playwright's built-in retry mechanisms by directly asserting on the expected state—assertions automatically retry until timeout.

Also applies to: 23-23, 34-34

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-virtual/e2e/app/test/cached-measurements.spec.ts` at line 10,
The test uses fixed page.waitForTimeout calls which are flaky; replace each
await page.waitForTimeout(...) with condition-based waits that assert the
expected DOM state (e.g., waitForFunction or
expect(locator).toHaveText()/toHaveCount()) so Playwright will retry until
stable; target the specific selectors used in the test (for example the element
with data-testid="total-size" or the list/container locators referenced in
cached-measurements.spec.ts) and wait for the numeric/text value or item count
to reach the expected value instead of sleeping, updating all three occurrences
to use these condition-based assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/react-virtual/e2e/app/test/cached-measurements.spec.ts`:
- Line 10: The test uses fixed page.waitForTimeout calls which are flaky;
replace each await page.waitForTimeout(...) with condition-based waits that
assert the expected DOM state (e.g., waitForFunction or
expect(locator).toHaveText()/toHaveCount()) so Playwright will retry until
stable; target the specific selectors used in the test (for example the element
with data-testid="total-size" or the list/container locators referenced in
cached-measurements.spec.ts) and wait for the numeric/text value or item count
to reach the expected value instead of sleeping, updating all three occurrences
to use these condition-based assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 97a605b7-5359-4c03-9a7a-4614ce3d89b0

📥 Commits

Reviewing files that changed from the base of the PR and between c0b84c8 and 320f97c.

📒 Files selected for processing (7)
  • .changeset/use-cached-measurements.md
  • docs/api/virtualizer.md
  • packages/react-virtual/e2e/app/cached-measurements/index.html
  • packages/react-virtual/e2e/app/cached-measurements/main.tsx
  • packages/react-virtual/e2e/app/test/cached-measurements.spec.ts
  • packages/react-virtual/e2e/app/vite.config.ts
  • packages/virtual-core/src/index.ts

@piecyk piecyk merged commit fbf3bdb into TanStack:main Jun 2, 2026
10 checks passed
@github-actions github-actions Bot mentioned this pull request Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant