Skip to content

fix(solid-virtual): defer non-sync onChange updates to a microtask - #1261

Draft
moreal wants to merge 2 commits into
TanStack:mainfrom
moreal:fix/solid-defer-non-sync-onchange
Draft

fix(solid-virtual): defer non-sync onChange updates to a microtask#1261
moreal wants to merge 2 commits into
TanStack:mainfrom
moreal:fix/solid-defer-non-sync-onchange

Conversation

@moreal

@moreal moreal commented Aug 27, 2026

Copy link
Copy Markdown

🎯 Changes

The Solid adapter applies every onChange immediately, including the ones virtual-core reports with sync: false. Those come from resizeItem() / measure(), which a row triggers from its own ref — while <For> is still iterating the virtual item store. reconcile then mutates that array in place, mapArray reads past the shortened array, and the row callback receives undefined:

TypeError: Cannot read properties of undefined (reading 'index')
 ❯ mapper solid-js/dist/dev.js:1229

virtual-core already distinguishes the two cases, and react-virtual acts on it (flushSync(rerender) when sync, plain rerender() otherwise). The Solid adapter received sync and only forwarded it to the user callback.

This applies the same distinction: scroll-driven updates stay synchronous, size changes are flushed in a microtask — after the current pass, before paint. Option changes (count, scrollMargin, …) also stay synchronous, since they run in Solid's reactive update phase rather than mid-render.

Behavior change: reading getVirtualItems() / getTotalSize() in the same tick as resizeItem() or measure() now returns the previous value. Reading before the re-render gives the same result in React, so this makes the adapters consistent. One existing test asserted the old timing and was updated.

tests/measure-during-render.test.tsx renders a <For> list with a measuring row ref and fails with the exception above without this change. The ref writes data-index before measuring because Solid emits _$use(ref, el) ahead of _$setAttribute (#930) — that workaround is what Solid consumers have to write today for measurement to run at all.

Related: #1216 and #790 touch the same onChange block. Neither fixes this — I applied #1216's change (dropping reconcile) locally and the reproduction still throws, because the problem is when the store is written, not how it is diffed.

✅ 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).

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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.

moreal added 2 commits August 28, 2026 01:51
- avoids reconciling the item store while <For> is iterating it

Assisted-by: choco-pi:claude-opus-5
Signed-off-by: Lee Dogeon <dev.moreal@gmail.com>
- jsdom render; pre-fix adapter throws the reported TypeError

Assisted-by: choco-pi:claude-opus-5
Signed-off-by: Lee Dogeon <dev.moreal@gmail.com>
@moreal
moreal force-pushed the fix/solid-defer-non-sync-onchange branch from f10ae18 to 7bc2dec Compare August 27, 2026 16:51
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