-
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
Add registry param to withDispatch component #11851
Conversation
Like I'd mentioned at #11495 (comment), I'm not really a fan of the idea, because inevitably I think a developer would expect to be able to... it( 'provides selected data as a prop?', () => {
registry.registerStore( 'counter', {
reducer( state = 0, action ) {
if ( action.type === 'increment' ) {
return state + 1;
}
return state;
},
actions: {
increment: () => ( { type: 'increment' } ),
},
selectors: {
getCount: ( state ) => state,
},
} );
const Component = withDispatch( ( _dispatch, ownProps, _select ) => {
const count = _select( 'counter' ).getCount();
return {
count,
increment: _dispatch( 'counter' ).increment,
};
} )( ( props ) => (
<button onClick={ props.increment }>
{ props.count }
</button>
) );
const testRenderer = TestRenderer.create(
<RegistryProvider value={ registry }>
<Component />
</RegistryProvider>
);
registry.dispatch( 'counter' ).increment();
expect( testRenderer.root.findByType( 'button' ).props.children ).toBe( 1 );
} ); |
Haha, you caught me. This is what I was planning to do: diff --git a/packages/editor/src/components/copy-handler/index.js b/packages/editor/src/components/copy-handler/index.js
index e74cde91b..fb66758c3 100644
--- a/packages/editor/src/components/copy-handler/index.js
+++ b/packages/editor/src/components/copy-handler/index.js
@@ -26,18 +26,7 @@ class CopyHandler extends Component {
}
onCopy( event ) {
- const { hasMultiSelection, selectedBlockClientIds, getBlocksByClientId } = this.props;
-
- if ( selectedBlockClientIds.length === 0 ) {
- return;
- }
-
- // Let native copy behaviour take over in input fields.
- if ( ! hasMultiSelection && documentHasSelection() ) {
- return;
- }
-
- const serialized = serialize( getBlocksByClientId( selectedBlockClientIds ) );
+ const serialized = serialize( this.props.getSelectedBlocks() );
event.clipboardData.setData( 'text/plain', serialized );
event.clipboardData.setData( 'text/html', serialized );
@@ -65,7 +54,6 @@ export default compose( [
const {
getMultiSelectedBlockClientIds,
getSelectedBlockClientId,
- getBlocksByClientId,
hasMultiSelection,
} = select( 'core/editor' );
@@ -75,13 +63,28 @@ export default compose( [
return {
hasMultiSelection: hasMultiSelection(),
selectedBlockClientIds,
+ };
+ } ),
+ withDispatch( ( dispatch, ownProps, select ) => {
+ const { getBlocksByClientId } = select( 'core/editor' );
+ const { removeBlocks } = dispatch( 'core/editor' );
+
+ return {
+ getSelectedBlocks: function() {
+ const { hasMultiSelection, selectedBlockClientIds } = ownProps;
+
+ if ( selectedBlockClientIds.length === 0 ) {
+ return;
+ }
+
+ // Let native copy behaviour take over in input fields.
+ if ( ! hasMultiSelection && documentHasSelection() ) {
+ return;
+ }
- // We only care about this value when the copy is performed
- // We call it dynamically in the event handler to avoid unnecessary re-renders.
- getBlocksByClientId,
+ return serialize( getBlocksByClientId( selectedBlockClientIds ) );
+ },
+ onRemove: removeBlocks,
};
} ),
- withDispatch( ( dispatch ) => ( {
- onRemove: dispatch( 'core/editor' ).removeBlocks,
- } ) ),
] )( CopyHandler ); |
In considering this further, and in attention of a common pattern of components receiving props for the sole purpose of shuffling them through to the dispatch callback in the limited cases at which they are relevant (example), I'll retract my hesitation. If we can assure that the properties of the return value of |
It has been addressed with 99d290b. I added unit test which ensures that function will throw an error when something other than function will throw an error. |
So this makes now editor explode :) |
99d290b
to
5711d9f
Compare
Added 6eb6826 which fixes the previous issue. |
5711d9f
to
a22ef38
Compare
a22ef38
to
6eb6826
Compare
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.
Less included refactoring is better if we want it merged sooner than later.
6eb6826
to
cead66c
Compare
@aduth, it's ready for another review. |
Moving this to 4.6, no rush to get it in today. |
d9925fe
to
4ce08ba
Compare
Tested the latest changes with the following scenario:
|
…HEAD * 'master' of https://github.com/WordPress/gutenberg: [RNmobile] Fix problems with undo/redo on Android (#12417) Add registry param to withDispatch component (#11851) Autocompleters: Consider block category (#12287) Only init TinyMCE once per instance (#12386) RichText: convert HTML formatting whitespace to spaces (#12166) Notices: Remove "files" block in package.json (#12438) Edit Post: Avoid rendering AdminNotices compatibility component (#12444) Correct the docs manifest (#12411)
@@ -7622,7 +7622,7 @@ | |||
}, | |||
"eslint-plugin-react": { | |||
"version": "7.7.0", | |||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.7.0.tgz", | |||
"resolved": "http://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.7.0.tgz", |
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.
Yes, I've been seeing it every now and then for a few weeks. It's very annoying behavior of npm. Probably some subtle bug recently introduced :(
@gziolo I know I'm very late to the party -- I found this PR when reviewing the If an action needs to know the latest state, that's a very common use case for Redux thunks: const startSale = () => ( dispatch, getState ) => {
const stockNumber = getStockNumber( getState() );
const discountPercent = stockNumber > 50 ? 10 : 20;
return dispatch( { type: START_SALE, discountPercent } );
}; Then, using the Just FYI about what the classic vanilla Redux solution is. I don't know if it would apply to the problem that inspired this PR, I just hope it would. |
@jsnajdr - what you shared is another way of tackling that and would work for the example we have in the docs. The implementation we shipped was based on a particular use case which helped us to prevent re-render of all blocks when using arrow key presses. See the lines below: To apply your proposal to |
Actually, we already have a way to do that in actions with controls and generators. |
How would I do it? Create a synchronous control that selects from the registry? Something like this: controls = {
GET_STOCK_NUMBER() {
return select( 'my/store' ).getStockNumber();
}
};
function* startSale() {
const stockNumber = yield { type: GET_STOCK_NUMBER };
return { type: START_SALE, discountPercent };
} The code I wrote might be completely misguided, I haven't used a control in practice yet 🙂 |
I was thinking of a generic "select" control. Something like that is already used here |
Follow-up task with some preliminary investigation: #12890 |
// <SaleButton discountPercent="20">Start Sale!</SaleButton> | ||
``` | ||
|
||
In the majority of cases, it will be sufficient to use only two first params passed to `mapDispatchToProps` as illustrated in the previous example. However, there might be some very advanced use cases where using the `registry` object might be used as a tool to optimize the performance of your component. Using `select` function from the registry might be useful when you need to fetch some dynamic data from the store at the time when the event is fired, but at the same time, you never use it to render your component. In such scenario, you can avoid using the `withSelect` higher order component to compute such prop, which might lead to unnecessary re-renders of you component caused by its frequent value change. Keep in mind, that `mapDispatchToProps` must return an object with functions only. |
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 lost this useful information in the transition toward docgen-generated documentation, cc @nosolosw
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 so? I see the comment in the readme taken from the JSDoc block.
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... swear I'd seen this missing from the current documentation. But it's there, you're quite right. Sorry for falsely raising the alarms!
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.
No need to be sorry, I'm happily porting anything that's missing :) We ought to have great docs!
Description
In the majority of cases, it will be sufficient to use only two first params passed to
mapDispatchToProps
. However, there might be some very advanced use cases where usingselect
function might be used as a tool to optimize the performance of your component. It is useful when you need to fetch some dynamic data from the store at the time when the event is fired, but at the same time, you never use it to render your component. In such scenario, you can avoid using thewithSelect
higher order component to compute such prop, which might lead to unnecessary re-renders of you component caused by its frequent value change.TODO
How has this been tested?
Unit tests:
npm run test
cmd+c
.cmd+v
.cmd+x
.cmd+v
.Types of changes
Refactoring.
Checklist: