-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Blocks: Try new context API to simplify display of block controls #5029
Conversation
I really like the idea, this change in addition to the removal of the focus management will make block author's life easier. I'm always suspicious when it comes to context usage in React in general but I think this is a good usage for it. |
f8fa7f0
to
44b9536
Compare
I figured out that build is failing because Enzyme doesn't support new tags created by |
44b9536
to
b5a77d2
Compare
Updated with the recent changes in Should we also do a similar trick for |
lib/client-assets.php
Outdated
@@ -379,16 +379,16 @@ function gutenberg_register_vendor_scripts() { | |||
|
|||
gutenberg_register_vendor_script( | |||
'react', | |||
'https://unpkg.com/react@16.2.0/umd/react' . $react_suffix . '.js' | |||
'https://unpkg.com/react@16.3.0-alpha.1/umd/react' . $react_suffix . '.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.
I missed that, it needs to be updated...
475038b
to
34f68ee
Compare
34f68ee
to
3192ba7
Compare
blocks/block-controls/index.js
Outdated
*/ | ||
import { ifEditBlockSelected } from '../block-edit/context'; | ||
|
||
export function BlockControls( { controls, children } ) { |
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.
How can we inform the user about this backward compatibity change. I think it's fine to ship it because users are probably already testing isSelected
on their side but how can we inform them that it's not useful anymore?
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.
It isn't a breaking change, so it's more about the convenience of developers and assuring that they don't introduce subtle bug as noted in here. I don't think we can add a deprecation message in this case as it's passed down in case of BlockControls
and InspectorControls
.
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 was thinking maybe not a warning but we add a log message like this when registering custom blocks:
In this new Gutenberg Release, The `BlockControls`, `InspectorControls` and `RichText` automatically hides when the block is not selected, you don't need to check or pass the `isSelected` prop anymore.
And just leave this message for one release at least? What do you think?
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.
Yes, I like this idea. I didn't think of it this way :)
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 believe we should push this further and update the default value of the isSelected
prop of the RichText
component to be true
now and wrap the Formatting Toolbar of the RichText component with this new HoC as well.
@youknowriad thanks for the review. @mcsf or @aduth would you mind confirming it is good to go? |
blocks/block-edit/index.js
Outdated
|
||
export class BlockEdit extends Component { | ||
constructor( props ) { | ||
super( props ); | ||
this.state = { |
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 don't think a component needs both the initial setting of state and static getDerivedStateFromProps
. The latter supersedes the need for the former. The constructor could be dropped altogether.
https://codepen.io/aduth/pen/QmRZLO
Edit: That said, it appears to be needed if you plan to return null;
from static getDerivedStateFromProps
, since otherwise the initial value of this.state
is undefined
.
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.
Yes, I also expected you can defer creating the initial state, but it complains that it is undefined.
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.
Since getDerivedStateFromProps
is always called on the initial mount, do we really need to set the values of state in the constructor? Or can we just set it to an empty object?
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.
Yes, let's do that. We will probably have to use get
to check state, but in overall it's still better.
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.
Much better: fbb66c8
* | ||
* @return {Component} Component which renders only when the BlockEdit is selected. | ||
*/ | ||
export const ifBlockEditSelected = createHigherOrderComponent( ( OriginalComponent ) => { |
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 like this 👍
@@ -57,6 +57,15 @@ export function initializeEditor( id, post, settings ) { | |||
const target = document.getElementById( id ); | |||
const reboot = reinitializeEditor.bind( null, target, settings ); | |||
|
|||
if ( 'production' !== process.env.NODE_ENV ) { | |||
// Remove with 3.0 release. |
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 wonder if we could implement this logging with an object property getter on a block's this.props.isSelected
, so we're not feeling compelled to remove it soon, and to not log when the message is not applicable (could be confusing, since a developer might assume there's something actively wrong with a block, even if there's not).
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
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.
isSelected
stays there, it is still useful in some other cases. Any ideas how to phrase it to ensure developers that their code will still work, but it can be simplified? Should we assume that updated docs are enough?
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.
My main concern is that often this is used as follows:
isSelected && (
<InspectorControls key="inspector">
...
</InspectorControls>
)
but also appears in a different context:
{ images.map( ( img, index ) => (
<li className="blocks-gallery-item" key={ img.id || img.url }>
<GalleryImage
url={ img.url }
alt={ img.alt }
id={ img.id }
isSelected={ isSelected && this.state.selectedImage === index }
onRemove={ this.onRemoveImage( index ) }
onSelect={ this.onSelectImage( index ) }
setAttributes={ ( attrs ) => this.setImageAttributes( index, attrs ) }
caption={ img.caption }
/>
</li>
) ) }
It is very hard to handle it on a case by case basis.
@@ -18,8 +18,8 @@ function BlockToolbar( { block, mode } ) { | |||
return ( | |||
<div className="editor-block-toolbar"> | |||
<BlockSwitcher uids={ [ block.uid ] } /> | |||
<Slot name="Block.Toolbar" /> | |||
<Slot name="Formatting.Toolbar" /> | |||
<BlockControls.Slot /> |
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.
Glad we're losing the magic strings 👍
docs/deprecated.md
Outdated
@@ -9,6 +9,7 @@ Gutenberg's deprecation policy is intended to support backwards-compatibility fo | |||
## 2.7.0 | |||
|
|||
- `wp.element.getWrapperDisplayName` function removed. Please use `wp.element.createHigherOrderComponent` instead. | |||
- `isSelected` usage is no longer mandatory with `BlockControls`, `InspectorControls` and `RichText`. It is now handled by the editor internally to ensure that controls are visible only when block is selected. See updated docs: https://github.com/WordPress/gutenberg/blob/master/blocks/README.md#components. |
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 is a bit out of place; the document is grouped by versions where specific features are removed, as a hint to developers to ensure updating in time. In this case, there's nothing backwards-incompatible.
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.
Well, yes. It's tricky. I will remove and ask @mtias to include in the release post.
</Fill> | ||
} | ||
{ isSelected && inlineToolbar && | ||
{ isSelected && ! inlineToolbar && ( |
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 isSelected
here redundant? Since BlockFormatControls
is already wrapped with ifBlockEditSelected
.
Edit: I see we have specific handling of isSelected
in this component. What's the use case for 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.
The usecase is for multiple RichText blocks (quote for example)
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.
Confusing (two tiers of isSelected
), but okay 😄
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.
Yes, multiple RichText is quite difficult to handle and maintain. I will look into it next week. Maybe it wouldn't be that hard to automatically handle active state in a way where only one RichText can be active per block at a time.
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.
If we would have isBlockSelected
and isRichTextSelected
it would be easier to read.
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.
Maybe it wouldn't be that hard to automatically handle active state in a way where only one RichText can be active per block at a time.
I think we can have only one active RichText for the whole post. Maybe we can just use a component id and store id of RichText that is active, it would simplify the logic of some 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.
I like the idea of storing a single active RichText in the editor store, if this is what you mean. It would be useful for #5794, so that inline images could be inserted anywhere RichText is used (not just in the Paragraph block), and so that the global inserter could select the state of the active RichText in the editor store when determining whether to render the Inline Blocks menu. CC @nb
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 think we can have only one active RichText for the whole post. Maybe we can just use a component id and store id of RichText that is active, it would simplify the logic of some blocks.
Yes, that's true. In addition, there might be only one active RichText per selected block. So I suppose we can reuse Context API for the same purpose and store the unique id of the actually active RichText component to make sure we don't display controls for the remaining components.
@@ -224,8 +226,6 @@ class ParagraphBlock extends Component { | |||
/> | |||
</PanelBody> | |||
</InspectorControls> | |||
), | |||
<div key="editable"> |
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 appears useless.. but are we sure it was?
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 can revert, but I didn't discover any issues so far in my testing. There are 5-ish more div wrappers there 🤣
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 will revert this particular change to be on the safe side, but I will try to remove it anyway in a separate PR when tackling this nested div
s hell :)
…k is selected This makes it backward compatible with the existing core blocks
c74fbcb
to
cf63e10
Compare
Moving forward with this one. There is no easy way to provide a message to the developers that |
Description
This PR updated React version to version 16.3.x (once it is stable we can update 15cd537 and move to its own PR). One of the new features that 16.3 is going to introduce is new context API. I gave it a spin to simplify the way we handle
<InspectorControls />
and<BlockContols />
insideedit
component included in every block.Before
After
Reasoning
I never liked that we need to prepend those controls with the check for every block. It is very easy to forget about it and it leads to unexpected behaviors. It turns out that this is a quite common issue that people have when learning how to create a new block. This is what I understood when talking to @zgordon. He shared also an example issue where the current logic caused troubles: Duplicate Inspector Controls.
Implementation
This PR wraps
<EditBlock />
component, which rendersedit
component for each block, with its own context which storesisSelected
value inside a consumer for later use. Both controls (<InspectorControls />
and<BlockContols />
) get wrapped with a context consumer which renders them only whenisSelected
is truthy. As simple as that.The same technique was used for
<RichText />
component and its< BlockFormatControls />
after suggestion from @youknowriad .How Has This Been Tested?
Manually tested if there were no regression. The only block that was upgraded so far is
<Paragraph />
to use this new context. If we agree that this is the change we want, I'm happy to update all existing blocks (as a follow-up)and the documentation(docs updated).The best way to test is to add a few paragraphs and make sure the display only their own controls.
I also made sure all unit tests pass (
npm run test
) and e2e tests pass on Travis.TODO
isSelected
is no longer mandatory.Checklist: