Skip to content

List View: Speed up opening by removing a second render pass - #80935

Merged
Mamaduka merged 3 commits into
trunkfrom
perf/list-view-fixed-list
Aug 2, 2026
Merged

List View: Speed up opening by removing a second render pass#80935
Mamaduka merged 3 commits into
trunkfrom
perf/list-view-fixed-list

Conversation

@Mamaduka

@Mamaduka Mamaduka commented Jul 30, 2026

Copy link
Copy Markdown
Member

What?

Builds on top of #80929.

Makes useFixedWindowList skip re-rendering its initial measurement triggers when the already-rendered window covers the visible items.

Only applies while the default initWindowSize of 30 covers the viewport
(panels up to ~1080px); taller panels keep the old two-pass path.

Why?

Opening List View committed twice inside one click: 31 placeholder rows render, a layout effect measures the scroll container, then setFixedListWindow forces a second synchronous render before paint. Each commit re-dirties the style, and each one incurs a full forced style recalculation; the recalc cost is per-pass overhead, roughly independent of how many rows the commit inserts, so the second pass was ~as expensive as the first for 5 extra rows.

How?

On the initial measurement, keep the current window if it already spans
[firstViewableIndex, firstViewableIndex + visibleItems]. The pre-measurement window (30 items) is normally a superset of the measured window, so nothing needs to be re-rendered. Later measurements (scroll/resize) update as before, so the window still shrinks while scrolling.

visibleItems moves to a ref, so measuring no longer re-renders on its own and Page Up/Down pages by the measured viewport rather than the initial guess.

Only applies while the default initWindowSize of 30 covers the viewport (panels up to ~1080px); taller panels keep the old two-pass path.

Testing Instructions

  • CI checks are passing.
  • Smoke test List View virtualization, it should work as before.

Testing Instructions for Keyboard

Same.

Screenshots or screencast

The last two Perf checks on run CI for listViewOpen metric.

CleanShot 2026-07-30 at 15 23 39 CleanShot 2026-07-30 at 14 23 53

Use of AI Tools

Assisted by Claude.

@github-actions github-actions Bot added the [Package] Compose /packages/compose label Jul 30, 2026
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

Size Change: +36 B (0%)

Total Size: 7.78 MB

📦 View Changed
Filename Size Change
build/scripts/compose/index.min.js 11.7 kB +36 B (+0.31%)

compressed-size-action

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

Flaky tests detected in eb12240.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/30704554054
📝 Reported issues:

@Mamaduka
Mamaduka force-pushed the perf/list-view-fixed-list branch from 71463fb to 2ea2f43 Compare July 30, 2026 10:30
@Mamaduka Mamaduka added the [Type] Performance Related to performance efforts label Jul 30, 2026
@Mamaduka Mamaduka self-assigned this Jul 30, 2026
@Mamaduka
Mamaduka marked this pull request as ready for review July 30, 2026 10:32
@Mamaduka
Mamaduka requested a review from ajitbohra as a code owner July 30, 2026 10:32
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Co-authored-by: jsnajdr <jsnajdr@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@Mamaduka Mamaduka added the [Feature] List View Menu item in the top toolbar to select blocks from a list of links. label Jul 31, 2026
Base automatically changed from perf/list-view-opening-aria-ref to trunk July 31, 2026 05:44
@Mamaduka
Mamaduka requested a review from ellatrix as a code owner July 31, 2026 05:44

@tyxla tyxla left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The perf numbers look convincing, nice speedup!

Left a few questions.

Also since #80929 has landed on trunk, this branch should be rebased.

lastWindow.start <= firstViewableIndex &&
lastWindow.end >= firstViewableIndex + visibleItems
) {
return lastWindow;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe we should avoid leaving fixedListWindow.visibleItems stale here? Right now, consumers continue receiving the previous value of visibleItems until a scroll or resize occurs.

@Mamaduka Mamaduka Jul 31, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That should be okay, because I don't think we can keep this value interactive and retain perf fix, which should be a fine trade-off IMO.

Honestly, I'm not sure why the hook returns this value; there's not much consumers can do with it. Event List View only used the itemInView.

P.S. I think we should start looking into adopting a battle-tested virtualization library rather than maintaining our own. But that's something for the future.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I explored @tanstack/react-virtual a little bit. It might be hard to adapt to our exact use case in the list view. And anyway, we have only a single use case for this right now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The idea of the patch is: at the moment we're deciding whether to update the nextWindow or not, we already did most of the work of rendering the window content. So, if the nextWindow is a strict subset of lastWindow, there's no point bothering to rerender a smaller window. We should rerender if some items are missing.

But this can happen not only on first measurement, but also any time later! Can we expand the optimization so that it never makes the window strictly smaller, but only expands it or slides it up/down?

Comment thread packages/compose/src/hooks/use-fixed-window-list/index.ts Outdated
Comment thread packages/compose/src/hooks/use-fixed-window-list/index.ts Outdated
@Mamaduka
Mamaduka force-pushed the perf/list-view-fixed-list branch from 2ea2f43 to 9f48af3 Compare July 31, 2026 11:12

@tyxla tyxla left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good, thank you @Mamaduka 🚀

I appreciate the extra listener cleanup 🙌

@jsnajdr

jsnajdr commented Jul 31, 2026

Copy link
Copy Markdown
Member

Some ideas for further refactoring:

Both effects should have scrollContainer as a dependency. Especially the second one, the one that attaches keydown listeners. Listeners should definitely be re-attached when scrollContainer changes. The elementRef dep is pointless, it's always constant.

Debouncing with debounceMeasureList by 16ms is not needed. The resize and scroll events are already fired at approx the rendering rate. For 60Hz rate, debouncing by 16ms doesn't reduce the frequency.

The hook doesn't need to return the setFixedListWindow setter, there is no use case to set it manually. Is this for backward compatibility? We can return a noop function in that case.

The two effects should be organized differently. The first one only measures the window when there is a reason to measure it:

useLayoutEffect( () => {
  measureWindow();
}, [ reasons, to, remeasure ] );

Reasons include change of totalItems, itemHeight. To stop exhaustive deps warnings, the dependencies will probably need to be on a measureWindow = useCallback assignment.

The second effect should only attach and remove listeners. Its only dependency should be scrollContainer. All data that the attached listeners use should be in refs. Or in a useCallback closure.

There is no reason why a change in totalItems should reattach listeners.

@Mamaduka
Mamaduka force-pushed the perf/list-view-fixed-list branch from 9f48af3 to eb12240 Compare August 1, 2026 14:48
@Mamaduka

Mamaduka commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

Thanks for the feedback, @jsnajdr!

I started refactoring and had Claude running perf metrics against the baseline in the background. The results are a bit different than what you suggested.

  • Had to keep the first measurement guard. Otherwise, we don't really get any performance wins from this refactoring.
  • Had to keep totalItems dep. Avoids cases where List View is opened on a short list, blocks are added until it overflows, and scrolling then never re-measures.
  • scrollContainer as a dep. This would fix the case above, but I couldn't find a way to do it without giving back the perf win. Making it reactive means putting it in state, and on mount elementRef.current is null during render, so the container can only be discovered in a layout effect - setScrollContainer( el ) there is precisely the extra render-and-commit before paint that this PR removes.

fixedListWindow.visibleItems,
useWindowing,
options?.expandedState,
elementRef,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The elementRef is only here to satisfy ESLint.

@jsnajdr

jsnajdr commented Aug 1, 2026

Copy link
Copy Markdown
Member

The results are a bit different than what you suggested.

That's fair, the ideas probably need some adjustment to make them good 🙂

Thank for the existing improvement, let's merge.

@Mamaduka
Mamaduka merged commit 4641cbd into trunk Aug 2, 2026
46 of 47 checks passed
@Mamaduka
Mamaduka deleted the perf/list-view-fixed-list branch August 2, 2026 06:00
@github-actions github-actions Bot added this to the Gutenberg 23.8 milestone Aug 2, 2026
@youknowriad

Copy link
Copy Markdown
Contributor

I love finding this kind of PRs :) The results are clear on the perf graph.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] List View Menu item in the top toolbar to select blocks from a list of links. [Package] Compose /packages/compose [Type] Performance Related to performance efforts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

Sponsor
SponsoredKunjungi sekarang
Promo