Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2385,6 +2385,21 @@ const getAllowedPatternsDependants = ( select ) => ( state, rootClientId ) => [
...getInsertBlockTypeDependants( state, rootClientId ),
];

const patternsWithParsedBlocks = new WeakMap();
Copy link
Copy Markdown
Member

@kevin940726 kevin940726 Oct 16, 2024

Choose a reason for hiding this comment

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

Maybe it's just me, but this type of manual optimization feels fragile to me. Why do we want to use a WeakMap to cache individual patterns rather than relying on composing cached selectors? Based on the implementation in getAllPatterns, we create a new array (and new items in the array) every time getAllPatternsDependants changes, breaking the memoization here. That is, every time the patterns list changes (new patterns, removed patterns, etc), we have to recompute all these anyway. The WeakMap doesn't really have a difference from a list-level selector. I have not tested this though so I could be very wrong here 😅. It just seems like we're aggressively doing memoization with different techniques without an idiomatic mental model (or that I'm lacking it 🤦). Memoization isn't free after all. I wonder if anyone else is feeling lost sometimes too 🤔.

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.

I wonder if anyone else is feeling lost sometimes too

Yes, I personally also feel lost all the time. Memoization is indeed extremely hard to understand 100%. It's probably the biggest downside of the Redux architecture and React in general. They depend a lot on exact identity and equality of objects, and the underlying JS language primitives don't have a good support for that.

Based on the implementation in getAllPatterns, we create a new array (and new items in the array) every time getAllPatternsDependants changes, breaking the memoization here.

There is a difference between memoizing the entire array, and memoizing the individual items. Getting allowed patterns for different block IDs legitimately produces different arrays, but our issue was that even though the individual patterns are exactly the same, the pattern objects were not the same. Because each call of the selector creates a new object for each array items: adding the get blocks enhancement.

Why do we want to use a WeakMap to cache individual patterns rather than relying on composing cached selectors?

A cached selector must have state as the first argument, it needs to select from the Redux state. But here we're working only with the pattern object, we don't have the state.

function enhancePatternWithParsedBlocks( pattern ) {
let enhancedPattern = patternsWithParsedBlocks.get( pattern );
if ( ! enhancedPattern ) {
enhancedPattern = {
...pattern,
get blocks() {
return getParsedPattern( pattern ).blocks;
},
};
patternsWithParsedBlocks.set( pattern, enhancedPattern );
}
return enhancedPattern;
}

/**
* Returns the list of allowed patterns for inner blocks children.
*
Expand All @@ -2406,14 +2421,7 @@ export const __experimentalGetAllowedPatterns = createRegistrySelector(
const { allowedBlockTypes } = getSettings( state );
const parsedPatterns = patterns
.filter( ( { inserter = true } ) => !! inserter )
.map( ( pattern ) => {
return {
...pattern,
get blocks() {
return getParsedPattern( pattern ).blocks;
},
};
} );
.map( enhancePatternWithParsedBlocks );

const availableParsedPatterns = parsedPatterns.filter(
( pattern ) =>
Expand Down