-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Block Editor]: Fix content loss from replaceInnerBlocks
with controlled blocks
#41948
[Block Editor]: Fix content loss from replaceInnerBlocks
with controlled blocks
#41948
Conversation
Size Change: +247 B (0%) Total Size: 1.25 MB
ℹ️ View Unchanged
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the prompt fix! Testing with the original reproduction steps everything now works as expected. I also tested adding/removing columns from a Columns block in a regular post, with both a Navigation block and a reusable block inside, and no content is lost. (On trunk, the same flow results in the content of both the Navigation and reusable block disappearing.)
Offhand I can't think how updating the state tree here might cause any harm, but I don't know this part of the code in-depth, so you might want to double check that 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work figuring this out. In trunk
the tree is definitely computed incorrectly, and in this branch I couldn't find a situation where it's incorrect.
I had quite a deep dive into the code to try to understand it. In terms of the solution, I find it difficult to say whether the controlled block should be reattached to the tree as it is in this PR, or whether it shouldn't be removed in the first place. To explain what I mean, as part of replacing the inner blocks, the blocks are first removed and then reinserted. There's a keepControlledInnerBlocks
property of the REMOVE_BLOCKS
action:
gutenberg/packages/block-editor/src/store/reducer.js
Lines 678 to 682 in f6f0cea
stateAfterBlocksRemoval = reducer( stateAfterBlocksRemoval, { | |
type: 'REMOVE_BLOCKS', | |
keepControlledInnerBlocks: nestedControllers, | |
clientIds: state.order[ action.rootClientId ], | |
} ); |
But the withBlockTree
higher order reducer doesn't seem to respect that prop and removes those clientIds anyway:
gutenberg/packages/block-editor/src/store/reducer.js
Lines 383 to 395 in f6f0cea
newState.tree = updateParentInnerBlocksInTree( | |
newState, | |
omit( | |
newState.tree, | |
action.removedClientIds.concat( | |
action.removedClientIds.map( | |
( clientId ) => 'controlled||' + clientId | |
) | |
) | |
), | |
parentsOfRemovedBlocks, | |
true | |
); |
An alternative solution might be just to keep them in the root, but I don't know for sure—maybe that would have some incorrect consequences.
Whatever the solution, it'd be good to add some unit tests to this PR if it's not too difficult.
Thanks for the reviews!
That is something I wasn't sure as well, but all these flows are interconnected with multiple actions and also events in I will see to write some tests.. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to my untrained eye, based on the knowledge I could brush up around controlled blocks. Thanks for the comprehensive test, too.
I haven't done any comprehensive testing, so more than anything I trust your judgment.
What?
Fixes: #41887
When we dispatch
REPLACE_INNER_BLOCKS
action we have a special handling which consists of two steps:In this flow(
withReplaceInnerBlocks
) we have some special handling for controlled blocks(reusable, postContent, etc..) where we keep them in state during the removal(keepControlledInnerBlocks
) and we are updating theorder
again after insertion.What we are missing is the update of
block tree
. I think the only reason we missed that until now is because in our code base we have just a few calls toreplaceInnerBlocks
, where most of them(if not all) aren't affected by this bug - example use cases would be replacing blocks with entirely new blocks or replacing blocks that only specific blocks are allowed(non controlled blocks).Testing Instructions(also can be seen in the video)
edit
the templatePostContent
toColumns
and add a new columns(this is where the call toreplaceInnerBlocks
happens).Before
before.mov
After
after.mov
Notes
remove blocks and replace blocks
with have similar handling in a different flow(REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN
,REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN
)controlled
blocks are the blocks that are a separate entity likereusable blocks, template parts, etc..
.