diff --git a/packages/solid-virtual/src/index.tsx b/packages/solid-virtual/src/index.tsx index 9f16672a..088a9dfc 100644 --- a/packages/solid-virtual/src/index.tsx +++ b/packages/solid-virtual/src/index.tsx @@ -15,7 +15,7 @@ import { onCleanup, onMount, } from 'solid-js' -import { createStore, reconcile } from 'solid-js/store' +import { createStore } from 'solid-js/store' import type { PartialKeys, VirtualizerOptions } from '@tanstack/virtual-core' export * from '@tanstack/virtual-core' @@ -71,11 +71,7 @@ function createVirtualizerBase< sync: boolean, ) => { instance._willUpdate() - setVirtualItems( - reconcile(instance.getVirtualItems(), { - key: 'index', - }), - ) + setVirtualItems(instance.getVirtualItems()) setTotalSize(instance.getTotalSize()) options.onChange?.(instance, sync) }, diff --git a/packages/solid-virtual/tests/filter-items.test.ts b/packages/solid-virtual/tests/filter-items.test.ts new file mode 100644 index 00000000..2712c5f3 --- /dev/null +++ b/packages/solid-virtual/tests/filter-items.test.ts @@ -0,0 +1,51 @@ +import { expect, test } from 'vitest' +import { createRoot, createSignal } from 'solid-js' + +import { createVirtualizer } from '../src/index' + +test('virtual items correctly index into filtered data after reactive shrink', () => { + createRoot((dispose) => { + const all = ['apple', 'banana', 'cherry', 'date', 'fig', 'grape'] + const [query, setQuery] = createSignal('') + + const filtered = () => { + const q = query() + if (!q) return all + return all.filter(s => s.includes(q)) + } + + const virtualizer = createVirtualizer({ + get count() { + return filtered().length + }, + getScrollElement: () => null, + estimateSize: () => 60, + initialRect: { width: 800, height: 600 }, + }) + + expect(virtualizer.getVirtualItems().length).toBe(6) + expect(virtualizer.getVirtualItems().map(v => filtered()[v.index])) + .toEqual(['apple', 'banana', 'cherry', 'date', 'fig', 'grape']) + + setQuery('e') + + expect(virtualizer.getVirtualItems().length).toBe(4) + const items = virtualizer.getVirtualItems() + expect(items.map(v => filtered()[v.index])) + .toEqual(['apple', 'cherry', 'date', 'grape']) + + setQuery('zz') + + expect(virtualizer.getVirtualItems().length).toBe(0) + expect(virtualizer.getVirtualItems().map(v => filtered()[v.index])) + .toEqual([]) + + setQuery('cherry') + + expect(virtualizer.getVirtualItems().length).toBe(1) + expect(virtualizer.getVirtualItems().map(v => filtered()[v.index])) + .toEqual(['cherry']) + + dispose() + }) +}) diff --git a/packages/solid-virtual/tests/reactivity.test.ts b/packages/solid-virtual/tests/reactivity.test.ts new file mode 100644 index 00000000..d202e5af --- /dev/null +++ b/packages/solid-virtual/tests/reactivity.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from 'vitest' +import { createRoot, createSignal } from 'solid-js' +import { unwrap } from 'solid-js/store' +import { createVirtualizer } from '../src/index' + +describe('reactivity: unaffected slots keep stable references', () => { + it('does not recreate VirtualItem objects for slots whose data did not change', () => { + createRoot((dispose) => { + const data = Array.from({ length: 50 }, (_, i) => `item-${i}`) + const [filtered, setFiltered] = createSignal(data) + + const virtualizer = createVirtualizer({ + get count() { + return filtered().length + }, + getScrollElement: () => document.createElement('div'), + estimateSize: () => 30, + overscan: 0, + }) + + const before = virtualizer.getVirtualItems() + const beforeRefs = before.map((item) => unwrap(item)) + + // Shrink the array; leaves the first visible rows' index/start/end/size untouched. + setFiltered(data.slice(0, 40)) + + const after = virtualizer.getVirtualItems() + const afterRefs = after.map((item) => unwrap(item)) + + const unaffectedCount = Math.min(beforeRefs.length, afterRefs.length) + for (let i = 0; i < unaffectedCount; i++) { + if ( + beforeRefs[i].start === afterRefs[i].start && + beforeRefs[i].end === afterRefs[i].end && + beforeRefs[i].index === afterRefs[i].index + ) { + expect(afterRefs[i]).toBe(beforeRefs[i]) + } + } + + dispose() + }) + }) +})