Skip to content

fix: add workaround for gutter to update vertical gap#48

Merged
2wheeh merged 1 commit into
mainfrom
fix/gutter
Jul 7, 2026
Merged

fix: add workaround for gutter to update vertical gap#48
2wheeh merged 1 commit into
mainfrom
fix/gutter

Conversation

@2wheeh

@2wheeh 2wheeh commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Dynamic gutter changes were not applied — virtual-core doesn't invalidate its measurement cache when gap changes (TanStack/virtual#1222).

Workaround: call virtualizer.measure() when gutter changes, forcing a re-layout. Covered by a test asserting it fires only on actual gutter changes.

Revert once the upstream fix lands.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed masonry layouts so spacing updates correctly when the gap between items changes.
    • Improved layout recalculation to avoid stale measurements after gutter changes.
  • Tests

    • Added coverage to verify the layout re-measures once when spacing changes and does not remeasure unnecessarily when it stays the same.
  • Chores

    • Added a patch release note for the updated behavior.

@changeset-bot

changeset-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 001a15a

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

This PR includes changesets to release 1 package
Name Type
react-virtual-masonry 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

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-virtual-masonry-docs Ready Ready Preview, Comment Jul 7, 2026 11:34am

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a workaround in useMasonry that tracks the previous gutter value and calls virtualizer.measure() when it changes, includes a new test verifying this remeasurement behavior, and adds a changeset entry documenting the patch.

Changes

Gutter Remeasure Workaround

Layer / File(s) Summary
Gutter change detection and remeasure
package/src/hooks/useMasonry.ts
Adds a prevGutter ref and useEffect that calls virtualizer.measure() when the gutter value changes.
Tests and changelog
package/src/hooks/useMasonry.test.tsx, .changeset/purple-seas-thank.md
Adds a test mocking useWindowVirtualizer's measure to verify it's called once on gutter change and not on unchanged rerenders, plus a changeset entry for the patch release.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Component
  participant useMasonry
  participant Virtualizer

  Component->>useMasonry: render with new gutter prop
  useMasonry->>useMasonry: compare gutter to prevGutter ref
  alt gutter changed
    useMasonry->>useMasonry: update prevGutter ref
    useMasonry->>Virtualizer: measure()
  else gutter unchanged
    useMasonry-->>Component: no remeasure triggered
  end
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding a gutter workaround to refresh the vertical gap.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gutter

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.

@2wheeh 2wheeh marked this pull request as ready for review July 7, 2026 11:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
package/src/hooks/useMasonry.ts (1)

128-136: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use useLayoutEffect to avoid a visible flash of stale spacing.

virtualizer.measure() runs in a passive useEffect, which fires after the browser paints. On a gutter change, the DOM will briefly render with stale cached item positions (the exact bug this workaround targets) before the effect fires and corrects them, causing a one-frame flicker. Community workarounds for this same TanStack Virtual caching issue use useLayoutEffect specifically to flush the remeasure synchronously before paint.

🎨 Proposed fix
-import { useEffect, useRef, useState } from 'react';
+import { useLayoutEffect, useEffect, useRef, useState } from 'react';
...
   const prevGutter = useRef(gutter);
-  useEffect(() => {
+  useLayoutEffect(() => {
     if (prevGutter.current !== gutter) {
       prevGutter.current = gutter;
       virtualizer.measure();
     }
   }, [gutter, virtualizer]);

Note: keep useEffect for the unrelated setMounted effect above; only this remeasure effect benefits from the layout-timing change.

🤖 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 `@package/src/hooks/useMasonry.ts` around lines 128 - 136, The gutter-change
remeasure in useMasonry is running too late in a passive effect, which can cause
a one-frame flash of stale virtualized spacing. Change the specific effect that
compares prevGutter.current and calls virtualizer.measure() from useEffect to
useLayoutEffect so the remeasure happens before paint, while keeping the
separate setMounted effect as useEffect. Use the existing symbols prevGutter,
gutter, and virtualizer to locate the exact block.
🤖 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 `@package/src/hooks/useMasonry.ts`:
- Around line 128-136: The gutter-change remeasure in useMasonry is running too
late in a passive effect, which can cause a one-frame flash of stale virtualized
spacing. Change the specific effect that compares prevGutter.current and calls
virtualizer.measure() from useEffect to useLayoutEffect so the remeasure happens
before paint, while keeping the separate setMounted effect as useEffect. Use the
existing symbols prevGutter, gutter, and virtualizer to locate the exact block.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bec07e72-22f7-42d6-91d9-928a97796b48

📥 Commits

Reviewing files that changed from the base of the PR and between ee6ab69 and 001a15a.

📒 Files selected for processing (3)
  • .changeset/purple-seas-thank.md
  • package/src/hooks/useMasonry.test.tsx
  • package/src/hooks/useMasonry.ts

@2wheeh 2wheeh merged commit 670010c into main Jul 7, 2026
6 checks passed
@2wheeh 2wheeh deleted the fix/gutter branch July 7, 2026 11:57
@github-actions github-actions Bot mentioned this pull request Jul 7, 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