Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
"changelog": [
"@svitejs/changesets-changelog-github-compact",
{ "repo": "TanStack/virtual" }
"@changesets/changelog-github",
{ "repo": "TanStack/virtual", "disableThanks": true }
],
"commit": false,
"access": "public",
Expand Down
7 changes: 7 additions & 0 deletions benchmarks/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @tanstack/virtual-benchmarks

## 0.0.5

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.2

## 0.0.4

### Patch Changes
Expand Down
10 changes: 5 additions & 5 deletions benchmarks/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@tanstack/virtual-benchmarks",
"private": true,
"version": "0.0.4",
"version": "0.0.5",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -12,16 +12,16 @@
},
"dependencies": {
"@tanstack/react-virtual": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"react-virtuoso": "^4.15.0",
"react-window": "^2.2.4",
"virtua": "^0.49.0"
},
"devDependencies": {
"@playwright/test": "^1.53.1",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@types/react": "^19.2.16",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.5.2",
"typescript": "5.6.3",
"vite": "^6.4.2"
Expand Down
16 changes: 16 additions & 0 deletions docs/api/virtualizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,22 @@ When enabled, defers ResizeObserver measurement processing to the next animation

Only enable this option if you have a specific reason and have measured that it improves your use case.

### `useCachedMeasurements`

```tsx
useCachedMeasurements?: boolean
```

**Default:** `false`

When enabled, the default `measureElement` implementation skips DOM measurement and returns the previously cached size for each item (falling back to `estimateSize` if no cached size exists).

This is useful when the virtualized list is temporarily hidden (e.g. via `display: none` on a parent element). Without this option, the ResizeObserver fires with size `0` for all items when hidden, resetting all measurements. When the list becomes visible again, items may need to be re-measured, which can cause layout shifts.

**Usage:** Toggle this option to `true` before hiding the list and back to `false` when showing it. The ResizeObserver remains attached, so real measurements resume automatically when the flag is turned off and elements become visible again.

> ⚠️ This option only affects the default `measureElement`. If you provide a custom `measureElement`, you are responsible for handling this case yourself.

## Virtualizer Instance

The following properties and methods are available on the virtualizer instance:
Expand Down
70 changes: 70 additions & 0 deletions docs/framework/react/react-virtual.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,73 @@ const virtualizer = useVirtualizer({
useFlushSync: false, // Disable synchronous updates
})
```

### `directDomUpdates`

- **Type**: `boolean`
- **Default**: `false`
- **Description**: Skip React re-renders for scroll-only updates. When enabled, the virtualizer writes item positions (`top`/`left` or `transform`) and the container size (`height`/`width`) directly to the DOM, and only re-renders when the visible index range or `isScrolling` changes.

#### Requirements when enabled

- Item elements must be `position: absolute`; in `'transform'` mode they must also be anchored with `top: 0` / `left: 0`.
- Item elements must **not** set the main-axis position in their style — the virtualizer owns `top` / `left` in `'position'` mode and `transform` in `'transform'` mode.
- The inner size container must receive `virtualizer.containerRef` and must **not** set `height` / `width` in its style.
- For multi-lane layouts (grids / masonry), the cross-axis position (e.g. `left: ${(item.lane * 100) / lanes}%`) is stable per item and must still be set in your JSX — only the main axis is automated.

> ⚠️ This flag is intended to be set once at mount. Toggling it (or `directDomUpdatesMode`) at runtime can leave stale inline styles on items and the container.

#### Example

```tsx
const virtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
directDomUpdates: true,
})

return (
<div ref={parentRef} style={{ overflow: 'auto', height: 400 }}>
{/* The inner container must use virtualizer.containerRef and not set height */}
<div ref={virtualizer.containerRef} style={{ position: 'relative' }}>
{virtualizer.getVirtualItems().map((item) => (
<div
key={item.key}
ref={virtualizer.measureElement}
data-index={item.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
// Do NOT set top/left/transform — the virtualizer handles it
}}
>
Row {item.index}
</div>
))}
</div>
</div>
)
```

### `directDomUpdatesMode`

- **Type**: `'position' | 'transform'`
- **Default**: `'transform'`
- **Description**: Controls how `directDomUpdates` positions item elements.
- `'transform'` (default): writes `transform: translate3d(...)`. Promotes items to their own compositor layer — usually smoother on long lists, but creates a stacking context and can interfere with `position: fixed` descendants. Item elements must be anchored with `position: absolute`, `top: 0`, and `left: 0`.
- `'position'`: writes `top` / `left`. Item elements must be `position: absolute`.

#### Example

```tsx
const virtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
directDomUpdates: true,
directDomUpdatesMode: 'position', // Use top/left instead of transform
})
```
2 changes: 1 addition & 1 deletion examples/angular/dynamic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"@faker-js/faker": "^8.4.1",
"@tanstack/angular-virtual": "^5.0.3",
"@tanstack/angular-virtual": "^5.0.4",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/fixed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"@tanstack/angular-virtual": "^5.0.3",
"@tanstack/angular-virtual": "^5.0.4",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/infinite-scroll/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"@tanstack/angular-query-experimental": "5.80.7",
"@tanstack/angular-virtual": "^5.0.3",
"@tanstack/angular-virtual": "^5.0.4",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/padding/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"@tanstack/angular-virtual": "^5.0.3",
"@tanstack/angular-virtual": "^5.0.4",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/smooth-scroll/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"@tanstack/angular-virtual": "^5.0.3",
"@tanstack/angular-virtual": "^5.0.4",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/sticky/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"@faker-js/faker": "^8.4.1",
"@tanstack/angular-virtual": "^5.0.3",
"@tanstack/angular-virtual": "^5.0.4",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/table/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@angular/router": "^19.0.0",
"@faker-js/faker": "^8.4.1",
"@tanstack/angular-table": "8.21.3",
"@tanstack/angular-virtual": "^5.0.3",
"@tanstack/angular-virtual": "^5.0.4",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/variable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"@tanstack/angular-virtual": "^5.0.3",
"@tanstack/angular-virtual": "^5.0.4",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/window/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"@tanstack/angular-virtual": "^5.0.3",
"@tanstack/angular-virtual": "^5.0.4",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
4 changes: 2 additions & 2 deletions examples/lit/dynamic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
},
"dependencies": {
"@faker-js/faker": "^8.4.1",
"@tanstack/lit-virtual": "^3.13.28",
"@tanstack/virtual-core": "^3.16.1",
"@tanstack/lit-virtual": "^3.13.29",
"@tanstack/virtual-core": "^3.17.0",
"lit": "^3.3.0"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions examples/lit/fixed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
},
"dependencies": {
"@faker-js/faker": "^8.4.1",
"@tanstack/lit-virtual": "^3.13.28",
"@tanstack/virtual-core": "^3.16.1",
"@tanstack/lit-virtual": "^3.13.29",
"@tanstack/virtual-core": "^3.17.0",
"lit": "^3.3.0"
},
"devDependencies": {
Expand Down
10 changes: 5 additions & 5 deletions examples/react/chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
"serve": "vite preview"
},
"dependencies": {
"@tanstack/react-virtual": "^3.14.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"@tanstack/react-virtual": "^3.14.2",
"react": "^19.2.7",
"react-dom": "^19.2.7"
},
"devDependencies": {
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@types/react": "^19.2.16",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.5.2",
"typescript": "5.6.3",
"vite": "^6.4.2"
Expand Down
10 changes: 5 additions & 5 deletions examples/react/dynamic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
},
"dependencies": {
"@faker-js/faker": "^8.4.1",
"@tanstack/react-virtual": "^3.14.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"@tanstack/react-virtual": "^3.14.2",
"react": "^19.2.7",
"react-dom": "^19.2.7"
},
"devDependencies": {
"@types/node": "^24.5.2",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@types/react": "^19.2.16",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.5.2",
"typescript": "5.6.3",
"vite": "^6.4.2"
Expand Down
10 changes: 5 additions & 5 deletions examples/react/fixed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
"serve": "vite preview"
},
"dependencies": {
"@tanstack/react-virtual": "^3.14.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"@tanstack/react-virtual": "^3.14.2",
"react": "^19.2.7",
"react-dom": "^19.2.7"
},
"devDependencies": {
"@types/node": "^24.5.2",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@types/react": "^19.2.16",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.5.2",
"typescript": "5.6.3",
"vite": "^6.4.2"
Expand Down
10 changes: 5 additions & 5 deletions examples/react/infinite-scroll/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
},
"dependencies": {
"@tanstack/react-query": "^5.80.7",
"@tanstack/react-virtual": "^3.14.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"@tanstack/react-virtual": "^3.14.2",
"react": "^19.2.7",
"react-dom": "^19.2.7"
},
"devDependencies": {
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@types/react": "^19.2.16",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.5.2",
"vite": "^6.4.2"
}
Expand Down
10 changes: 5 additions & 5 deletions examples/react/padding/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
"start": "vite"
},
"dependencies": {
"@tanstack/react-virtual": "^3.14.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"@tanstack/react-virtual": "^3.14.2",
"react": "^19.2.7",
"react-dom": "^19.2.7"
},
"devDependencies": {
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@types/react": "^19.2.16",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.5.2",
"vite": "^6.4.2"
}
Expand Down
10 changes: 5 additions & 5 deletions examples/react/pretext/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
},
"dependencies": {
"@chenglou/pretext": "^0.0.7",
"@tanstack/react-virtual": "^3.14.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"@tanstack/react-virtual": "^3.14.2",
"react": "^19.2.7",
"react-dom": "^19.2.7"
},
"devDependencies": {
"@types/node": "^24.5.2",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@types/react": "^19.2.16",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.5.2",
"typescript": "5.6.3",
"vite": "^6.4.2"
Expand Down
2 changes: 1 addition & 1 deletion examples/react/pretext/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function estimateMessageHeight(message: Message, viewportWidth: number) {
)
}

function useElementWidth(ref: React.RefObject<HTMLElement>) {
function useElementWidth(ref: React.RefObject<HTMLElement | null>) {
const [width, setWidth] = React.useState(DEFAULT_VIEWPORT_WIDTH)

React.useLayoutEffect(() => {
Expand Down
10 changes: 5 additions & 5 deletions examples/react/scroll-padding/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
},
"dependencies": {
"@react-hookz/web": "^25.1.1",
"@tanstack/react-virtual": "^3.14.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"@tanstack/react-virtual": "^3.14.2",
"react": "^19.2.7",
"react-dom": "^19.2.7"
},
"devDependencies": {
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@types/react": "^19.2.16",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.5.2",
"vite": "^6.4.2"
}
Expand Down
Loading
Loading