-
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
Avoid automatic saving of uncontrolled Nav blocks #39883
Avoid automatic saving of uncontrolled Nav blocks #39883
Conversation
Size Change: +474 B (0%) Total Size: 1.22 MB
ℹ️ View Unchanged
|
@talldan I'm particularly pinging you here because I know you did a lot of work on handling uncontrolled inner blocks and you're very likely to be able to notice edge cases 🙏 |
...I need to fix the e2e tests and add a new one to assert that making inner blocks dirty will trigger conversion to entity. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
// If the current inner blocks object is different in any way | ||
// from the original inner blocks from the post content then the | ||
// user has made changes to the inner blocks. At this point the inner |
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.
I should update this comment. It's not about whether object contents are different. It's about the object references changing if the block data changes.
This comment was marked as outdated.
This comment was marked as outdated.
|
||
// Initially store the uncontrolled inner blocks for | ||
// dirty state comparison. | ||
if ( ! originalBlocks?.current ) { |
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.
Is modifying the ref directly like this advised? Would it be better to put the ref directly on the component?
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.
What makes you concerned here?
The ref is a stored value which we create solely to store whatever blocks the component is passed as its initial value.
We do nothing with originalBlocks
in terms of passing it to <InnerBlocks>
. Its sole purpose is to act as a source of truth for the original "state" of the blocks that was originally passed to the component.
I'm going to double check this checks out correctly and I'm not setting us up for bugs.
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.
I guess my concern is whether we are abusing the ref
feature of React - why not just us a local variable?
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.
Because a ref is guaranteed to be the same reference for the lifetime of the component whereas the local variable will be a new reference on each render of the component:
The returned object will persist for the full lifetime of the component (React Docs)
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.
So basically I'm using the ref to store the "source of truth" for the lifecycle of the component. Then if at any point the current blocks don't match up with the original source of truth reference stored in the ref
then the blocks have changed because the references are different.
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.
Would it be possible to replace this ref with a combination of hasUncontrolledInnerBlocks
and hasUnsavedBlocks
that's used in edit/index.js?
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.
Basically I wonder if this information can be derived from somewhere instead of computing it here
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.
hasUnsavedBlocks
is computed here as a product of
- whether there is an entity
- whether there are uncontrolled inner blocks present (this is determined by
getBlock( clientId ).innerBlocks
)
const hasUnsavedBlocks = hasUncontrolledInnerBlocks && ! isEntityAvailable; |
Neither of those factors tell us whether the inner blocks have had modifications. They simply report that
- there is no
wp_navigation
entity associated with the current block. - there are inner blocks present.
But can't we use the "saved" state to determine whether there are changes to the blocks? Unfortunately not. Why? Because that's putting the cart before the horse.
We need the uncontrolled version of the Nav block to save (convert to entity mode) only when we detect there have been modifications to those blocks. Therefore we can't use the "saved" state to determine whether the blocks have changed because that state should be a product of whether the blocks have changed.
As far as I can see the only way to determine if the uncontrolled inner blocks have changed is to effectively memoize the initial value of blocks (i.e. getBlock( clientId ).innerBlocks
) and then check that to see if it changes.
This is what I'm trying to do with the ref. I save the initial value and then compare that reference against the new incoming value. When the user makes a change to the inner blocks the object returned by getBlock( clientId ).innerBlocks
will change and thus we can compare the references and determine they are no longer equal.
I might be able to do that outside the component but I'm not sure what the value of that would be.
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.
Wow, great exploration @getdave 👍 alright, let's stick to using the ref then. If this happens more often, then we could look into abstracting it out into a reusable API.
packages/block-library/src/navigation/edit/unsaved-inner-blocks.js
Outdated
Show resolved
Hide resolved
React docs explain this can lead to undesired side effects
0fd77ee
to
82a0d8d
Compare
82a0d8d
to
b44cbe1
Compare
packages/block-library/src/navigation/edit/unsaved-inner-blocks.js
Outdated
Show resolved
Hide resolved
You cannot reset a block that has uncontrolled inner blocks
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 like a good improvement. Ideally the new navigation shouldn't be created until I hit the save button. I added an issue for that here: #40050
// If the block has inner blocks, but no menu id, then these blocks are either: | ||
// - inserted via a pattern. | ||
// - inserted directly via Code View (or otherwise). | ||
// - from an older version of navigation block added before the block used a wp_navigation entity. |
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.
Can't they be added directly by the theme as well?
This makes adding a new item quite weird: flicker-link-control.mp4 |
What?
This PR updates the Nav block so that if it has uncontrolled blocks (i.e. those not coming from a
wp_navigation
entity but rather those provided directly as<-- wp:block
) then it will not automatically convert them into an entity.Instead the block will now wait for the blocks to become "dirty" (i.e. have been changed) before converting into a
wp_navigation
.Part of #38157 and/or #39489
Why?
There are a number of ways where a Nav block might contain raw inner blocks. For example:
Taking the former example from TT2 here is the block definition:
Notice how the Page List block is provided in order that there is default content in the block when the Site Editor is loaded.
The current problem with this is that as soon as the user interacts with the Nav block it will automatically convert the uncontrolled blocks into a
wp_navigation
post. There is no warning that this has occured and so the user is none the wiser.The result of this is that it's super easy to inadvertently create many
wp_navigation
posts simply by selecting Navigation blocks on your site.How?
This PR fixes thing by allowing the raw inner blocks to stay uncontrolled until such time as the user makes a modification to those blocks. As soon as that occurs then they will be auto-converted into a
wp_navigation
post and a notice will be displayed announcing that to the user.In future it might be nice to give the user some means to opt in/out of the conversion perhaps via a dialog. This PR follows te simplest path to fix the core problem of accidental creation of
wp_navigation
posts.Testing Instructions
ref
attribute on the Nav block.Edit
and thenConvert
.wp_navigation
entity will be created.ref
attribute on the Nav block.Screenshots or screencast