-
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
Avoid unnecessary re-renders when navigating between blocks #11495
Conversation
…cks with keyboard
return; | ||
} | ||
|
||
const { getBlockSelectionStart, onMultiSelect, onSelect } = this.props; |
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.
Actually, clientId
can be destructured, too.
|
||
const { getBlockSelectionStart, onMultiSelect, onSelect } = this.props; | ||
|
||
if ( getBlockSelectionStart() ) { |
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 really like the main refactoring of moving this handling to the individual block item, but I'm not sure how I feel about this specific change of calling a selector within the component itself. I don't think there's anything bad about it in the sense of generating inaccurate data, but it does feel like something which would be easy to overlook as far as the general pattern of providing data to components via a selector vs. passing the selector itself. That said, passing the selector does provide opportunities to optimize when the data is only needed in very specific circumstances like this.
Alternatively, I'm left to wonder if it's still something we could handle in the withDispatch
callback, receiving the selection start from withSelect
and somehow blocking it from reaching the component. I don't know that this is easily achievable without something like mapProps
(to drop the unwanted prop) and a separate layer of pure
. Not only is this pretty cumbersome, it probably has worse performance than if we were to just pass the data anyways.
A variation to this would be to still have the bulk of the handling occur in withDispatch
, but call select
to retrieve the value from getBlockSelectionStart
. It's not too much different than what we're doing here, so I don't know that it really is much better.
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.
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.
You had even described it as confusing when @iseulde had proposed the same thing in #10431
Yes, I remember that. I still think that it is an issue. That's why I added an inline comment :)
A variation to this would be to still have the bulk of the handling occur in
withDispatch
, but call select to retrieve the value fromgetBlockSelectionStart
. It's not too much different than what we're doing here, so I don't know that it really is much better.
I think the easiest change would be to add select
as 3rd param to withDispatch
HOC, which should work out of the box with the current implementation and would allow retrieving the value which causes this discussion inside the event handler without exposing it as a prop. Now, the question is if we want to confuse plugin developers with this new capability. I really like how symmetric and straightforward are withSelect
and withDispatch
as of today.
Alternatively, I'm left to wonder if it's still something we could handle in the
withDispatch
callback, receiving the selection start fromwithSelect
and somehow blocking it from reaching the component. I don't know that this is easily achievable without something likemapProps
(to drop the unwanted prop) and a separate layer ofpure
. Not only is this pretty cumbersome, it probably has worse performance than if we were to just pass the data anyways.
Redux solves this issue by offering 3rd param called mergeProps
inside connect
HOC. However, it's very difficult to use and even more difficult to read. Besides connect
is one HOC so you can avoid re-renders by filtering unwanted props, but the cost is that you have to perform lots of computations to upfront to make it work.
It would be great if we would come up with a pattern to ensure we have a common way of handling similar use cases. In general, I observe that there are many places where we compute props which are never used for rendering but they sit there and wait until a given event occurs.
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.
We can just pass select
as a third argument in withDispatch
for these advanced use-cases.
Edit I just saw that it was already proposed :)
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 really like the idea, partly because with how proxying works, we shouldn't imply that withDispatch
can be a method for injecting up-to-date state values to a component. In the very worst case, someone could even wrongly think to ignore withSelect
altogether and just use withDispatch
for all their needs.
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'm thinking more that wp.data.select
within the withDispatch
callback is maybe sensible, as its intended usage is for exactly these moments of one-off data retrieval.
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 the rerendering here a big issue to sacrifice devx like we're trying to do by finding solutions here? Maybe the current approach by @gziolo in the PR is just fine as it's well commented as a performance optimization. I'm thinking the generalization might not be worth it because it doesn't seem so common.
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 there is an easy solution, everything we do have some drawbacks. If you add 3rd params select
to withDispatch
it might be used incorrectly. Example: when you call selector outside of the event handler, it might not re-evaluate properly when the value you want to defer changes because it isn't reflected in props. So you still need to make sure it's called inside the handler.
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 the rerendering here a big issue to sacrifice devx like we're trying to do by finding solutions here?
Yes, I think so. More-so being that this is BlockListBlock
.
I see it as something we should consider having some guidelines around, since it's not the first time this has come up.
What's the drawback with calling the main select
(the one exported by @wordpress/data
) from the withDispatch
callback?
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 think so. More-so being that this is BlockListBlock.
Agreed with this, that we should make this particular component performant (and the current approach is also sacrificing good code, because passing selector is not great either but it's localized)
What I'm not certain about is the changes we need to do in the data module to accommodate this.
Should we proceed with the current approach and find a better solution when we have more use cases which would justify the need of introducing another param in |
Since I'm thinking my suggestion may have been unclear, this is what I had in mind: |
@aduth I thought I replied to this but apparently not :) I want us to avoid the singleton as much as we can going forward. The drawback is that you can be using a separate registry (think reusable block for instance) |
Oh, I see what you mean, since it wouldn't respect the registry via context in which the component is being rendered. |
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'm fine with this for now. I'm not sure how we can better make this more clear, but I think it's reasonable to have exceptional handling of selectors for this component in particular.
@@ -645,6 +665,9 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps ) => { | |||
onSelect( clientId = ownProps.clientId, initialPosition ) { | |||
selectBlock( clientId, initialPosition ); | |||
}, | |||
onMultiSelect( ...args ) { |
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 could just be onMultiSelect: multiSelect
Description
Another try to fix #11397 :)
As pointed out by @aduth
isSelectionEnabled
is almost alwaystrue
so this check shouldn't work. It might be a bug which was apparently fixed in the meantime or me being wrong when testing. Anyways, this PR takes a bit different approach.First of all, I moved
onShiftSelection
one level down as it doesn't depend onBlockList
at all. This should make easier reasoning about its behavior next to the code which actually uses it.Note to myself:
check if we can make it work as it would be a static method.- dismissed.The fix is documented inline. To avoid all re-renders we don't compute the value inside
withSelect
as it changes for allBlockListBlock
instances. Instead we pass down selector and call it dynamically only when it's necessary. It is very rare use case, as it only happens when user holds downshift
key and activates multi selection.How has this been tested?
Screenshots
Before
After
Types of changes
Refactoring, no changes to the logic.
Checklist: