Block Comments: New 'useBlockComments' hook and perf improvements#71869
Block Comments: New 'useBlockComments' hook and perf improvements#71869
Conversation
|
Size Change: -86 B (0%) Total Size: 1.95 MB
ℹ️ View Unchanged
|
| return getClientIdsWithDescendants().reduce( ( results, clientId ) => { | ||
| const commentId = getBlockAttributes( clientId )?.blockCommentId; | ||
| if ( commentId ) { | ||
| results[ commentId ] = clientId; | ||
| } | ||
| return results; | ||
| }, {} ); |
There was a problem hiding this comment.
Note to self: Add a note about this special workaround.
It seems we're "forced" to do similar filter/map/reduce hacks more often. Maybe there's a better alternative. cc @jsnajdr, @talldan, @tyxla
Examples:
gutenberg/packages/patterns/src/components/overrides-panel.js
Lines 27 to 34 in 802a127
gutenberg/packages/block-editor/src/components/block-inspector/index.js
Lines 134 to 157 in 802a127
There was a problem hiding this comment.
What exactly is the "workaround" and "hack"? In all three code examples we select list of IDs with useSelect and getClientIdsWithDescendants and then transform the array somehow.
The transformation should always be outside the useSelect, inside useMemo, because it creates a new array even for identical input. Only the OverridesPanel example does it right. The other two will trigger a useSelect warning: "returns different values".
Hopefully React Compiler will help us automate this memoizing in the future.
The useSelect+useMemo pattern still has the disadvantage that there will be unneeded rerenders: useSelect result changes, but the useMemo result doesn't. In a truly ideal situation, the transformation would be wrapped in a memoized function:
const transform = memo( ( input ) => input.filter( ... ) );
const interestingIds = useSelect( ( select ) => transform( getClientIds() ) );
Here you get a rerender only when interestingIds really change.
There was a problem hiding this comment.
What exactly is the "workaround" and "hack"?
It's a workaround because the returned object or array will pass a shallow equality check. They'll have the following shapes:
// Example object.
{ 'client1': 1, 'client2': 2, ... }
// Example array.
[ 'client1', 'client2', ... ]
The other two will trigger a useSelect warning: "returns different values".
I'm not able to trigger these warnings for the mentioned components.
There was a problem hiding this comment.
Oh I see, there is the trick with returning the array itself instead of { result: theArray }. Then there's the shallow equality check. I agree it's a workaround.
For me, the ideal situation is that we have ergonomic tooling for doing the filter/reduce 1) inside useSelect 2) memoized. Something like:
const getCommentIds = useMemoizedCallback( ( ids ) => ids.reduce( ... ), [] );
const { blocksWithComments } = useSelect( ( select ) => {
const blocks = select( be ).getClientIdsWithDescendants();
return { blockWithComments: getCommentIds( blocks ) };
}, [] );
Here the useMemoizedCallback has some cache inside that resets when the dependencies change.
I think this could be a nice library addition that would have a lot of potential usage across wp-data-using codebases.
|
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 If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
2da67d9 to
61ef285
Compare
|
Tremendous! |
t-hamano
left a comment
There was a problem hiding this comment.
Great performance improvement! Regarding this comment, let's address it in a follow-up if necessary.
|
Thank you, folks! @t-hamano, yes, it's not a blocker. Happy to follow up as needed. |
|
cc: @jeffpaul in case you missed it |
|
Thanks @adamsilverstein, captured in #71963 |
What?
PR extracts all
block-commentsdata, querying logic into a separate hook.Additional improvements:
commentId -> blockClientIdmap. Avoiding unwanted re-renders when typing in Canvas. See screencast.blockClientIdto the top-level comment so that the value can be easily reused later.Note: I've only made the necessary changes to the comment tree structure logic. We could try improving it after #71728 (comment) is resolved.
Why?
Makes it easier to maintain and reuse, though the latter shouldn't be needed. Also fixes potential performance issues with typing when rendering large comment trees.
Testing Instructions
Testing Instructions for Keyboard
Same.
Screenshots or screencast
Before
CleanShot.2025-09-24.at.15.51.28.mp4
After
CleanShot.2025-09-24.at.15.48.55.mp4