-
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
Framework: Support registering and invoking actions in the data module #5137
Conversation
data/README.md
Outdated
wp.data.registerActions( 'myPlugin', { setTitle: ( newTitle ) => ( { type: 'SET_TITLE', title: newTitle } ); | ||
``` | ||
|
||
#### 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.
It's copied from registerSelectors
:)
d6190d6
to
2b5c933
Compare
In theorhy it should have a public API like: wp.data.dispatch( 'core/editor' )(
wp.data.actions( 'core/editor' ).insertBlocks( wp.blocks.createBlock( 'core/image' )
); to mirror what Redux does. However I like the shortcut you propose 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.
I'm very happy to see it. We have recently discussed with @Shelob9 in #5012, how important it is to make it possible to dispatch actions exposed by core and other plugins. I think this PR concludes the initial version of data
module 👏
We might add some safety checks in the follow-up PR which make sure that code will continue to work even when an action or selector got removed by one of the plugins.
export function dispatch( reducerKey ) {
return actions[ reducerKey ];
}
This will obviously error when someone calls:
wp.data.dispatch( 'remove-key-reducer' ).myAction();
We need to find a way to leave feedback for developers but at the same time make sure that the editor continues to work.
I think this is great. And agree this resolves #5012 I think that @gziolo point about needing to prevent I'd love if a test could be added to ensure dispatching a non-existant action is dispatched doesn't make an error to cover that case. |
|
This has been on my mind as well. One thing I was wondering is whether it could make sense to have two separate higher-order components for selecting and dispatching. We could mirror React-Redux Thoughts? |
This one is tricky, but for |
We can also expose the following: export const actions = mapDispatchToProps => query( undefined, mapDispatchToProps ); which would cover all use cases. I'm fine with all possibilities discussed 👍 |
No strong opinion for me on whether to expose this or not. A smaller preference towards a single API because it's similar to |
The other small worries I have are:
|
@aduth I'd be open to a name change, what do you propose? something like As I said, I'm not strongly against having three HigherOrderComponents. Let's discuss in #core-js |
Yes, all options have pros and cons. Let’s pick one at the core-js chat 👍 |
2b5c933
to
55619d1
Compare
Per discussions in #core-js slack, I introduced the
|
I've been thinking about this since the JavaScript meeting this morning, and I think the main complication comes from the fact that functions created within We also talked about how consolidating to one single-argument higher-order component could help avoid using Taking both these points into consideration, I have in mind a separate higher-order component Taking the examples here, this could look like: export default compose( [
withData( () => ( {
title: select( 'myPlugin' ).getTitle(),
} ),
withDispatch( {
updateTitle: dispatch( 'myPlugin' ).setTitle,
} ),
] )( Component ); And for the complex example raised in today's meeting: const { insertBlocks, updateBlockAttributes } = dispatch( 'core/editor' );
export default compose( [
withDispatch( {
onInsertBlocks: ( blocks, insertIndex ) => ( props ) => {
const { rootUID, layout } = props;
if ( layout ) {
// A block's transform function may return a single
// transformed block or an array of blocks, so ensure
// to first coerce to an array before mapping to inject
// the layout attribute.
blocks = castArray( blocks ).map( ( block ) => (
cloneBlock( block, { layout } )
) );
}
return insertBlocks( blocks, insertIndex, rootUID );
},
updateBlockAttributes,
} ),
// ...
] )( BlockDropZone ); This relies on the fact that
One difference with
This means we should be able to rewrite current usage of const {
showInsertionPoint,
hideInsertionPoint,
insertBlock,
replaceBlocks,
} = dispatch( 'core/editor' );
export default compose( [
withData( {
insertionPoint: select( 'core/editor' ).getBlockInsertionPoint,
selectedBlock: select( 'core/editor' ).getSelectedBlock,
} ),
withDispatch( {
showInsertionPoint,
hideInsertionPoint,
onInsertBlock: ( item ) => ( props ) => {
const { insertionPoint, selectedBlock } = props;
const { index, rootUID, layout } = insertionPoint;
const { name, initialAttributes } = item;
const insertedBlock = createBlock( name, { ...initialAttributes, layout } );
if ( selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) {
return replaceBlocks( selectedBlock.uid, insertedBlock );
}
return insertBlock( insertedBlock, index, rootUID );
},
} ),
] )( Inserter );
|
@aduth withDispatch( {
showInsertionPoint,
hideInsertionPoint,
onInsertBlock: ( item ) => ( props ) => {
const { insertionPoint, selectedBlock } = props;
const { index, rootUID, layout } = insertionPoint;
const { name, initialAttributes } = item;
const insertedBlock = createBlock( name, { ...initialAttributes, layout } );
if ( selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) {
return replaceBlocks( selectedBlock.uid, insertedBlock );
}
return insertBlock( insertedBlock, index, rootUID );
},
} ), how do you differentiate the more direct handlers |
My initial thinking is that what's passed into the component is a proxying function which calls the original value, and only until it returns an object does it dispatch. Otherwise it recursively calls with props of the component. Let me see if I can put together some (pseudo-)code. |
Might look something like this? export const withDispatch = ( propsToDispatchers ) => ( WrappedComponent ) => {
class ComponentWithDispatch extends Component {
constructor() {
super( ...arguments );
this.proxyProps = mapValues( propsToDispatchers, ( dispatcher, propName ) => {
return this.proxyDispatch.bind( this, propName );
} );
}
proxyDispatch( propName, ...args ) {
const result = propsToDispatchers[ propName ]( ...args );
if ( typeof result === 'function' ) {
result( this.props );
}
}
render() {
return <WrappedComponent { ...this.props } { ...this.proxyProps } />;
}
}
return ComponentWithDispatch;
}; |
@aduth The problem with this implementation is that if someones is extending its store to use Also, I'm concerned about the learning curve of this API. It's not that straightforward to understand, especially if you're not familiar with functional programming aspects (currying), redux-thunk and similar. Edit: Our action creators are auto-bound so maybe we won't have the problem with redux-thunk but we're assuming that dispatching actions don't return functions which is not guaranteed even if it's not common. |
Results of debugging:
|
1bcf2e4
to
c6846f3
Compare
@youknowriad Would it be possible to insert rendered blocks? Like:
Thanks! |
It is possible: something like wp.data.dispatch( 'core/editor' ).insertBlocks( wp.blocks.createBlock( 'core/paragraph', { align: 'right', content: [ '... like this one, which is right aligned.' ] } ) ); |
@youknowriad Thanks! So there's a need to convert the blocks first. It's fine though, I just thought I can add a directly html block code. Thanks again! |
@youknowriad Sorry, another one, How can I use |
@youknowriad nevermind my questions above :) Just figured it out! :) |
@youknowriad Another question if you don't mind. Just saw |
@phpbits yes, there is you can find all the available functions here https://github.com/WordPress/gutenberg/blob/master/editor/store/selectors.js |
@youknowriad Thank you very much! |
The same way we support registering and calling selectors, this PR adds support for registering and calling actions to the data module.
wp.data.dispatch
function taking a key and an action (similar to select)wp.data.registerActions
to register the exposed actions.mapDispatchToProps
in thequery
HoC.I implemented inserting a block as an exposed action in this PR.
As a follow-up, we could provide a
registerState
helper function to register everything at once:reducer, actions and selectors
.Testing instructions
wp.data.dispatch( 'core/editor' ).insertBlocks( wp.blocks.createBlock( 'core/image' ) );
in your browser.