Skip to content

fix: clamp initialItemCount to data length when data prop is used - #1461

Merged
petyosi merged 4 commits into
petyosi:mainfrom
aehmt:fix/clamp-initial-item-count-to-data-length
Sep 22, 2026
Merged

petyosi merged 4 commits into
petyosi:mainfrom
aehmt:fix/clamp-initial-item-count-to-data-length

Conversation

@aehmt

@aehmt aehmt commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Bug

When initialItemCount exceeds data.length, buildListStateFromItemCount creates phantom items with data: undefined. These phantom items produce zero-height DOM elements, triggering the "Zero-sized element, this should not happen" console error for each one.

Reproduction

<Virtuoso
  data={["A", "B", "C"]}       // 3 items
  initialItemCount={10}          // greater than data.length
  itemContent={(index, item) => <div>{item ?? "undefined!"}</div>}
  style={{ height: 400 }}
/>

Console shows Zero-sized element, this should not happen {child: div} repeated 7 times (10 - 3 = 7 phantom items).

Root cause

In buildListStateFromItemCount (listStateSystem.ts), the item count is not clamped to the data array length:

const adjustedCount = itemCount + includedGroupsCount
// adjustedCount can exceed data.length ^^^
const items = Array.from({ length: adjustedCount }).map((_, index) => ({
    data: data[index + initialTopMostItemIndexNumber],
    // ^^^ accesses indices beyond data.length → undefined
    ...
}))

Fix

Clamp adjustedCount to data.length when the data prop is provided (non-empty array):

const adjustedCount = data.length > 0
  ? Math.min(itemCount + includedGroupsCount, data.length)
  : itemCount + includedGroupsCount

When data is not used (totalCount-only mode, where data defaults to []), the existing behavior is preserved.

Relation to #1060

This is a separate code path from the issue fixed in #1061. That PR addressed a ResizeObserver timing issue in useSize. This fix addresses buildListStateFromItemCount never clamping to data.length — a bug that was present before and after #1061. The continued reports on #1060 (e.g., this comment on v4.8.0+) suggest this code path was the remaining unfixed cause.

Test added

Added a test in listSystem.test.ts verifying that initialItemCount is clamped to data.length and no items have undefined data.

When initialItemCount exceeds data.length, buildListStateFromItemCount
creates phantom items with data: undefined. These phantom items produce
zero-height DOM elements, triggering Zero-sized element console errors.

Clamp the adjusted count to data.length when the data prop is provided
(non-empty array), so Virtuoso never renders more initial items than exist
in the data array. When data is not used (totalCount-only mode), the
existing behavior is preserved.

Relates to petyosi#1060 -- the prior fix (PR petyosi#1061) addressed a ResizeObserver
timing issue in useSize, but this separate code path in
buildListStateFromItemCount was never patched.
@changeset-bot

changeset-bot Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 797e7a5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
react-virtuoso Patch
@virtuoso.dev/examples Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@petyosi

petyosi commented Jul 20, 2026

Copy link
Copy Markdown
Owner

This is technically a valid fix, but can you explain why would the library consumer pass such a value? As in, is this purely defensive improvement for an invalid value?

@aehmt

aehmt commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

This is technically a valid fix, but can you explain why would the library consumer pass such a value? As in, is this purely defensive improvement for an invalid value?

@petyosi It's not purely defensive, this is a natural mismatch that happens when initialItemCount is set to a fixed value to avoid the empty flash before Virtuoso measures the viewport. You don't always know how many items the server will return, you pick a generous number (e.g., 10) so the first paint isn't blank, but data may have fewer items:

<Virtuoso
  data={results}             // could be 3 items
  initialItemCount={10}      // generous default to fill viewport on first paint
  itemContent={(i, item) => <div>{item.name}</div>}
/>

This isn't an invalid value case, initialItemCount and data are independent props, and the library shouldn't produce phantom items with undefined data when one exceeds the other. Those phantom items render as zero-height DOM elements, which triggers the Zero-sized element, this should not happen console errors, noisy in production envoriments and confusing to debug. The clamp is a one line fix and preserves existing behavior when data isn't used (totalCount-only mode).

@aehmt
aehmt force-pushed the fix/clamp-initial-item-count-to-data-length branch from 52af5c1 to 6acb745 Compare July 21, 2026 17:20
@helloworldcyj

Copy link
Copy Markdown

We pass initialItemCount={Math.min(3, data.length)}, so the prop is always in range. The problem:

  1. In systemToComponent, props are published in the fixed key order of the optional map — data is published before initialItemCount. This happens both on mount and in the useLayoutEffect that re-publishes all props on every render.
  2. In listStateSystem, initialItemCount is not part of the reactive combineLatest — it is read synchronously from its stateful cell inside the listState computation.
  3. So on a render where data shrinks (3 → 2) and initialItemCount is lowered accordingly (3 → 2), publishing data first synchronously recomputes listState with the stale cell value (3) against the new, shorter data.
  4. If visibleRange is still [0, 0], the computation takes the buildListStateFromItemCount branch and generates data[2] === undefined — a phantom item.

When does visibleRange stay [0, 0]? Viewport measurement is asynchronous (ResizeObserver + requestAnimationFrame) and skipped for hidden elements (offsetParent === null guards in both the RO wrapper and the custom-scroll-parent listener). If the component mounts or re-mounts while the page is hidden — in our case an Android WebView covered by another native screen — the measurement never runs and visibleRange keeps its initial [0, 0] until the page becomes visible again (same behavior noted in #970). Any data update landing in that window walks straight into the unclamped path.

Production trace of one crash: list renders 3 items (cell = 3) → user opens another native screen, webview hidden before measurement completes → a background refresh on page-show removes one item → the same render publishes data (2 items) then initialItemCount (2) → the data publish recomputes listState with the stale cell value 3 → phantom item at index 2 → computeItemKey(2, undefined) throws → blank page.

So the clamp in this PR is not defensive hardening for an invalid prop — it is the minimal invariant that makes the library's own one-publish-cycle staleness harmless. We would really appreciate it landing.

Two small notes on the patch itself (optional, not blocking):

  • With initialTopMostItemIndex > 0, data[index + initialIndexNumber] can still overflow even after the length clamp; Math.min(adjustedCount, data.length - initialIndexNumber) would cover that case too.
  • Alternatively, making initialItemCount part of the combineLatest (or publishing it before data) would eliminate the stale window entirely — but the clamp alone already prevents the crash.

Follow-up to the data.length clamp, covering the remaining gaps:

- clamp the count to the data remaining after initialTopMostItemIndex,
  which could still read past the end of the array
- treat an explicitly empty data array as zero items, while omitted
  data keeps the index-driven initial rendering
- add initialItemCount to the list state combineLatest so publishing it
  recomputes immediately; previously it was read from a stale cell and
  a data shrink in the same render cycle produced undefined items,
  crashing computeItemKey in unmeasured (hidden) viewports
- add a 13-input combineLatest overload to urx
@petyosi
petyosi merged commit 364c071 into petyosi:main Sep 22, 2026
8 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 22, 2026
sickn33 added a commit to sickn33/agentic-awesome-skills that referenced this pull request Oct 16, 2026
![snyk-top-banner](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg)

<h3>Snyk has created this PR to upgrade react-virtuoso from 4.18.14 to 4.18.15.</h3>

:information_source: Keep your dependencies up-to-date. This makes it easier to fix existing vulnerabilities and to more quickly identify and fix newly disclosed vulnerabilities when they affect your project.

<hr/>

- The recommended version is **1 version** ahead of your current version.

- The recommended version was released **23 days ago**.

#### Breaking Change Risk

![Merge Risk: Medium](https://img.shields.io/badge/Merge%20Risk%3A%20Medium-8B4513?style=for-the-badge)

> **Notice:** This assessment is enhanced by AI.

<details>
<summary><b>Release notes</b></summary>
<br/>
  <details>
    <summary>Package name: <b>react-virtuoso</b></summary>
    <ul>
      <li>
        <b>4.18.15</b> - <a href="https://redirect.github.com/petyosi/react-virtuoso/releases/tag/react-virtuoso%404.18.15">2026-09-22</a></br><h3>Patch Changes</h3>
<ul>
<li><a href="https://redirect.github.com/petyosi/react-virtuoso/pull/1461" data-hovercard-type="pull_request" data-hovercard-url="/petyosi/react-virtuoso/pull/1461/hovercard">#1461</a> <a href="https://redirect.github.com/petyosi/react-virtuoso/commit/364c071dc1e044b52d1cfbce65851440d39be77e"><code>364c071</code></a> Thanks <a href="https://redirect.github.com/aehmt">@ aehmt</a>! - Fixed "Zero-sized element, this should not happen" error when <code>initialItemCount</code> exceeds the available <code>data</code>. The initial list state builder now clamps the item count to the data remaining after <code>initialTopMostItemIndex</code>, and renders no items when <code>data</code> is explicitly empty, preventing phantom items with <code>undefined</code> data from being rendered. <code>initialItemCount</code> also became a reactive input of the list state computation, so changing it after mount recomputes the list instead of leaving stale state until an unrelated update.</li>
</ul>
      </li>
      <li>
        <b>4.18.14</b> - 2026-09-20
      </li>
    </ul>
    from <a href="https://redirect.github.com/petyosi/react-virtuoso/releases">react-virtuoso GitHub release notes</a>
  </details>
</details>

---

> [!IMPORTANT]
>
> - Check the changes in this PR to ensure they won't cause issues with your project.
> - This PR was automatically created by Snyk using the credentials of a real user.

---

**Note:** _You are seeing this because you or someone else with access to this repository has authorized Snyk to open upgrade PRs._

**For more information:** <img src="https://api.segment.io/v1/pixel/track?data=eyJ3cml0ZUtleSI6ImZha2Uta2V5IiwiYW5vbnltb3VzSWQiOiJkZWU2Njk4NC1iOWViLTQ0ODktYjBhOS01NWRjMTJiNTczN2MiLCJldmVudCI6IlBSIHZpZXdlZCIsInByb3BlcnRpZXMiOnsicHJJZCI6ImRlZTY2OTg0LWI5ZWItNDQ4OS1iMGE5LTU1ZGMxMmI1NzM3YyJ9fQ==" width="0" height="0"/>

> - 🧐 [View latest project report](https://app.eu.snyk.io/org/antigravity-awesome-skills-default/project/e1f199d3-0a16-4ac7-a9a1-528e774fc41f?utm_source&#x3D;github&amp;utm_medium&#x3D;referral&amp;page&#x3D;upgrade-pr)
> - 📜 [Customise PR templates](https://docs.snyk.io/scan-using-snyk/pull-requests/snyk-fix-pull-or-merge-requests/customize-pr-templates?utm_source=&utm_content=fix-pr-template)
> - 🛠 [Adjust upgrade PR settings](https://app.eu.snyk.io/org/antigravity-awesome-skills-default/project/e1f199d3-0a16-4ac7-a9a1-528e774fc41f/settings/integration?utm_source&#x3D;github&amp;utm_medium&#x3D;referral&amp;page&#x3D;upgrade-pr)
> - 🔕 [Ignore this dependency or unsubscribe from future upgrade PRs](https://app.eu.snyk.io/org/antigravity-awesome-skills-default/project/e1f199d3-0a16-4ac7-a9a1-528e774fc41f/settings/integration?pkg&#x3D;react-virtuoso&amp;utm_source&#x3D;github&amp;utm_medium&#x3D;referral&amp;page&#x3D;upgrade-pr#auto-dep-upgrades)

[//]: # 'snyk:metadata:{"breakingChangeRiskLevel":"medium","FF_showPullRequestBreakingChanges":true,"FF_showPullRequestBreakingChangesWebSearch":false,"customTemplate":{"variablesUsed":[],"fieldsUsed":[]},"dependencies":[{"name":"react-virtuoso","from":"4.18.14","to":"4.18.15"}],"env":"prod","hasFixes":false,"isBreakingChange":false,"isMajorUpgrade":false,"issuesToFix":[],"prId":"dee66984-b9eb-4489-b0a9-55dc12b5737c","prPublicId":"dee66984-b9eb-4489-b0a9-55dc12b5737c","packageManager":"npm","priorityScoreList":[],"projectPublicId":"e1f199d3-0a16-4ac7-a9a1-528e774fc41f","projectUrl":"https://app.eu.snyk.io/org/antigravity-awesome-skills-default/project/e1f199d3-0a16-4ac7-a9a1-528e774fc41f?utm_source=github&utm_medium=referral&page=upgrade-pr","prType":"upgrade","templateFieldSources":{"branchName":"default","commitMessage":"default","description":"default","title":"default"},"templateVariants":[],"type":"auto","upgrade":[],"upgradeInfo":{"versionsDiff":1,"publishedDate":"2026-09-22T18:25:30.741Z"},"vulns":[]}'
sickn33 added a commit to sickn33/agentic-awesome-skills that referenced this pull request Oct 23, 2026
![snyk-top-banner](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg)

<h3>Snyk has created this PR to upgrade react-virtuoso from 4.18.15 to 4.18.16.</h3>

:information_source: Keep your dependencies up-to-date. This makes it easier to fix existing vulnerabilities and to more quickly identify and fix newly disclosed vulnerabilities when they affect your project.

<hr/>

- The recommended version is **1 version** ahead of your current version.

- The recommended version was released **24 days ago**.

#### Breaking Change Risk

![Merge Risk: Low](https://img.shields.io/badge/Merge%20Risk%3A%20Low-006400?style=for-the-badge)

> **Notice:** This assessment is enhanced by AI.

<details>
<summary><b>Release notes</b></summary>
<br/>
  <details>
    <summary>Package name: <b>react-virtuoso</b></summary>
    <ul>
      <li>
        <b>4.18.16</b> - <a href="https://redirect.github.com/petyosi/react-virtuoso/releases/tag/react-virtuoso%404.18.16">2026-09-29</a></br><h3>Patch Changes</h3>
<ul>
<li><a href="https://redirect.github.com/petyosi/react-virtuoso/pull/1512" data-hovercard-type="pull_request" data-hovercard-url="/petyosi/react-virtuoso/pull/1512/hovercard">#1512</a> <a href="https://redirect.github.com/petyosi/react-virtuoso/commit/b7238f3856ae51afcbf8045a7c61b8f1c42e9a0c"><code>b7238f3</code></a> Thanks <a href="https://redirect.github.com/cpruijsen">@ cpruijsen</a>! - Map <code>skipAnimationFrameInResizeObserver</code> through TableVirtuoso's urx optional props so the table resize observer consumes it instead of leaving the leftover prop on the scroller DOM.</li>
</ul>
      </li>
      <li>
        <b>4.18.15</b> - <a href="https://redirect.github.com/petyosi/react-virtuoso/releases/tag/react-virtuoso%404.18.15">2026-09-22</a></br><h3>Patch Changes</h3>
<ul>
<li><a href="https://redirect.github.com/petyosi/react-virtuoso/pull/1461" data-hovercard-type="pull_request" data-hovercard-url="/petyosi/react-virtuoso/pull/1461/hovercard">#1461</a> <a href="https://redirect.github.com/petyosi/react-virtuoso/commit/364c071dc1e044b52d1cfbce65851440d39be77e"><code>364c071</code></a> Thanks <a href="https://redirect.github.com/aehmt">@ aehmt</a>! - Fixed "Zero-sized element, this should not happen" error when <code>initialItemCount</code> exceeds the available <code>data</code>. The initial list state builder now clamps the item count to the data remaining after <code>initialTopMostItemIndex</code>, and renders no items when <code>data</code> is explicitly empty, preventing phantom items with <code>undefined</code> data from being rendered. <code>initialItemCount</code> also became a reactive input of the list state computation, so changing it after mount recomputes the list instead of leaving stale state until an unrelated update.</li>
</ul>
      </li>
    </ul>
    from <a href="https://redirect.github.com/petyosi/react-virtuoso/releases">react-virtuoso GitHub release notes</a>
  </details>
</details>

---

> [!IMPORTANT]
>
> - Check the changes in this PR to ensure they won't cause issues with your project.
> - This PR was automatically created by Snyk using the credentials of a real user.

---

**Note:** _You are seeing this because you or someone else with access to this repository has authorized Snyk to open upgrade PRs._

**For more information:** <img src="https://api.segment.io/v1/pixel/track?data=eyJ3cml0ZUtleSI6ImZha2Uta2V5IiwiYW5vbnltb3VzSWQiOiJiOTQxODU0OC1hNDU3LTQ2YjgtOTQxNi1iMTdjMzM1ZWFiNTkiLCJldmVudCI6IlBSIHZpZXdlZCIsInByb3BlcnRpZXMiOnsicHJJZCI6ImI5NDE4NTQ4LWE0NTctNDZiOC05NDE2LWIxN2MzMzVlYWI1OSJ9fQ==" width="0" height="0"/>

> - 🧐 [View latest project report](https://app.eu.snyk.io/org/antigravity-awesome-skills-default/project/e1f199d3-0a16-4ac7-a9a1-528e774fc41f?utm_source&#x3D;github&amp;utm_medium&#x3D;referral&amp;page&#x3D;upgrade-pr)
> - 📜 [Customise PR templates](https://docs.snyk.io/scan-using-snyk/pull-requests/snyk-fix-pull-or-merge-requests/customize-pr-templates?utm_source=&utm_content=fix-pr-template)
> - 🛠 [Adjust upgrade PR settings](https://app.eu.snyk.io/org/antigravity-awesome-skills-default/project/e1f199d3-0a16-4ac7-a9a1-528e774fc41f/settings/integration?utm_source&#x3D;github&amp;utm_medium&#x3D;referral&amp;page&#x3D;upgrade-pr)
> - 🔕 [Ignore this dependency or unsubscribe from future upgrade PRs](https://app.eu.snyk.io/org/antigravity-awesome-skills-default/project/e1f199d3-0a16-4ac7-a9a1-528e774fc41f/settings/integration?pkg&#x3D;react-virtuoso&amp;utm_source&#x3D;github&amp;utm_medium&#x3D;referral&amp;page&#x3D;upgrade-pr#auto-dep-upgrades)

[//]: # 'snyk:metadata:{"breakingChangeRiskLevel":"low","FF_showPullRequestBreakingChanges":true,"FF_showPullRequestBreakingChangesWebSearch":false,"customTemplate":{"variablesUsed":[],"fieldsUsed":[]},"dependencies":[{"name":"react-virtuoso","from":"4.18.15","to":"4.18.16"}],"env":"prod","hasFixes":false,"isBreakingChange":false,"isMajorUpgrade":false,"issuesToFix":[],"prId":"b9418548-a457-46b8-9416-b17c335eab59","prPublicId":"b9418548-a457-46b8-9416-b17c335eab59","packageManager":"npm","priorityScoreList":[],"projectPublicId":"e1f199d3-0a16-4ac7-a9a1-528e774fc41f","projectUrl":"https://app.eu.snyk.io/org/antigravity-awesome-skills-default/project/e1f199d3-0a16-4ac7-a9a1-528e774fc41f?utm_source=github&utm_medium=referral&page=upgrade-pr","prType":"upgrade","templateFieldSources":{"branchName":"default","commitMessage":"default","description":"default","title":"default"},"templateVariants":[],"type":"auto","upgrade":[],"upgradeInfo":{"versionsDiff":1,"publishedDate":"2026-09-29T16:03:47.020Z"},"vulns":[]}'
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.

3 participants