diff --git a/data/README.md b/data/README.md
index d95d3d7cce2ca..ce2f42e09274d 100644
--- a/data/README.md
+++ b/data/README.md
@@ -22,4 +22,43 @@ Registers a [`listener`](https://redux.js.org/docs/api/Store.html#subscribe) fun
#### `store.dispatch( action: object )`
-The dispatch function should be called to trigger the registered reducers function and update the state. An [`action`](https://redux.js.org/docs/api/Store.html#dispatch)object should be passed to this action. This action is passed to the registered reducers in addition to the previous state.
+The dispatch function should be called to trigger the registered reducers function and update the state. An [`action`](https://redux.js.org/docs/api/Store.html#dispatch) object should be passed to this function. This action is passed to the registered reducers in addition to the previous state.
+
+
+### `wp.data.registerSelectors( reducerKey: string, newSelectors: object )`
+
+If your module or plugin needs to expose its state to other modules and plugins, you'll have to register state selectors.
+
+A selector is a function that takes the current state value as a first argument and extra arguments if needed and returns any data extracted from the state.
+
+#### Example:
+
+Let's say the state of our plugin (registered with the key `myPlugin`) has the following shape: `{ title: 'My post title' }`. We can register a `getTitle` selector to make this state value available like so:
+
+```js
+wp.data.registerSelectors( 'myPlugin', { getTitle: ( state ) => state.title } );
+```
+
+### `wp.data.select( key: string, selectorName: string, ...args )`
+
+This function allows calling any registered selector. Given a module's key, a selector's name and extra arguments passed to the selector, this function calls the selector passing it the current state and the extra arguments provided.
+
+#### Example:
+
+```js
+wp.data.select( 'myPlugin', 'getTitle' ); // Returns "My post title"
+```
+
+### `wp.data.query( mapSelectorsToProps: func )( WrappedComponent: Component )`
+
+If you use a React or WordPress Element, a Higher Order Component is made available to inject data into your components like so:
+
+```js
+const Component = ( { title } ) =>
{ title }
;
+
+wp.data.query( select => {
+ return {
+ title: select( 'myPlugin', 'getTitle' ),
+ };
+} )( Component );
+```
diff --git a/data/index.js b/data/index.js
index 1cf4a779fd01a..841b5263ac054 100644
--- a/data/index.js
+++ b/data/index.js
@@ -1,6 +1,7 @@
/**
* External dependencies
*/
+import { connect } from 'react-redux';
import { createStore, combineReducers } from 'redux';
import { flowRight } from 'lodash';
@@ -8,6 +9,7 @@ import { flowRight } from 'lodash';
* Module constants
*/
const reducers = {};
+const selectors = {};
const enhancers = [];
if ( window.__REDUX_DEVTOOLS_EXTENSION__ ) {
enhancers.push( window.__REDUX_DEVTOOLS_EXTENSION__() );
@@ -17,17 +19,17 @@ const initialReducer = () => ( {} );
const store = createStore( initialReducer, {}, flowRight( enhancers ) );
/**
- * Registers a new sub reducer to the global state and returns a Redux-like store object.
+ * Registers a new sub-reducer to the global state and returns a Redux-like store object.
*
- * @param {String} key Reducer key
- * @param {Object} reducer Reducer function
+ * @param {string} reducerKey Reducer key.
+ * @param {Object} reducer Reducer function.
*
- * @returns {Object} Store Object.
+ * @returns {Object} Store Object.
*/
-export function registerReducer( key, reducer ) {
- reducers[ key ] = reducer;
+export function registerReducer( reducerKey, reducer ) {
+ reducers[ reducerKey ] = reducer;
store.replaceReducer( combineReducers( reducers ) );
- const getState = () => store.getState()[ key ];
+ const getState = () => store.getState()[ reducerKey ];
return {
dispatch: store.dispatch,
@@ -46,3 +48,55 @@ export function registerReducer( key, reducer ) {
getState,
};
}
+
+/**
+ * Registers selectors for external usage.
+ *
+ * @param {string} reducerKey Part of the state shape to register the
+ * selectors for.
+ * @param {Object} newSelectors Selectors to register. Keys will be used
+ * as the public facing API. Selectors will
+ * get passed the state as first argument.
+ */
+export function registerSelectors( reducerKey, newSelectors ) {
+ selectors[ reducerKey ] = newSelectors;
+}
+
+/**
+ * Higher Order Component used to inject data using the registered selectors.
+ *
+ * @param {Function} mapSelectorsToProps Gets called with the selectors object
+ * to determine the data for the component.
+ *
+ * @returns {Func} Renders the wrapped component and passes it data.
+ */
+export const query = ( mapSelectorsToProps ) => ( WrappedComponent ) => {
+ const connectWithStore = ( ...args ) => {
+ const ConnectedWrappedComponent = connect( ...args )( WrappedComponent );
+ return ( props ) => {
+ return ;
+ };
+ };
+
+ return connectWithStore( ( state, ownProps ) => {
+ const select = ( key, selectorName, ...args ) => {
+ return selectors[ key ][ selectorName ]( state[ key ], ...args );
+ };
+
+ return mapSelectorsToProps( select, ownProps );
+ } );
+};
+
+/**
+ * Calls a selector given the current state and extra arguments.
+ *
+ * @param {string} reducerKey Part of the state shape to register the
+ * selectors for.
+ * @param {string} selectorName Selector name.
+ * @param {*} args Selectors arguments.
+ *
+ * @returns {*} The selector's returned value.
+ */
+export const select = ( reducerKey, selectorName, ...args ) => {
+ return selectors[ reducerKey ][ selectorName ]( store.getState()[ reducerKey ], ...args );
+};
diff --git a/data/test/__snapshots__/index.js.snap b/data/test/__snapshots__/index.js.snap
new file mode 100644
index 0000000000000..46758a91bd19a
--- /dev/null
+++ b/data/test/__snapshots__/index.js.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`query passes the relevant data to the component 1`] = `
+
+ reactState
+
+`;
diff --git a/data/test/index.js b/data/test/index.js
index 4e0a2d68d4475..08d56fed3354d 100644
--- a/data/test/index.js
+++ b/data/test/index.js
@@ -1,4 +1,12 @@
-import { registerReducer } from '../';
+/**
+ * External dependencies
+ */
+import { render } from 'enzyme';
+
+/**
+ * Internal dependencies
+ */
+import { registerReducer, registerSelectors, select, query } from '../';
describe( 'store', () => {
it( 'Should append reducers to the state', () => {
@@ -12,3 +20,42 @@ describe( 'store', () => {
expect( store2.getState() ).toEqual( 'ribs' );
} );
} );
+
+describe( 'select', () => {
+ it( 'registers multiple selectors to the public API', () => {
+ const store = registerReducer( 'reducer1', () => 'state1' );
+ const selector1 = jest.fn( () => 'result1' );
+ const selector2 = jest.fn( () => 'result2' );
+
+ registerSelectors( 'reducer1', {
+ selector1,
+ selector2,
+ } );
+
+ expect( select( 'reducer1', 'selector1' ) ).toEqual( 'result1' );
+ expect( selector1 ).toBeCalledWith( store.getState() );
+
+ expect( select( 'reducer1', 'selector2' ) ).toEqual( 'result2' );
+ expect( selector2 ).toBeCalledWith( store.getState() );
+ } );
+} );
+
+describe( 'query', () => {
+ it( 'passes the relevant data to the component', () => {
+ registerReducer( 'reactReducer', () => ( { reactKey: 'reactState' } ) );
+ registerSelectors( 'reactReducer', {
+ reactSelector: ( state, key ) => state[ key ],
+ } );
+ const Component = query( ( selectFunc, ownProps ) => {
+ return {
+ data: selectFunc( 'reactReducer', 'reactSelector', ownProps.keyName ),
+ };
+ } )( ( props ) => {
+ return { props.data }
;
+ } );
+
+ const tree = render( );
+
+ expect( tree ).toMatchSnapshot();
+ } );
+} );
diff --git a/editor/store/index.js b/editor/store/index.js
index 4bb3318a78228..8da2e29e19aa9 100644
--- a/editor/store/index.js
+++ b/editor/store/index.js
@@ -1,7 +1,7 @@
/**
* WordPress Dependencies
*/
-import { registerReducer } from '@wordpress/data';
+import { registerReducer, registerSelectors } from '@wordpress/data';
/**
* Internal dependencies
@@ -12,11 +12,13 @@ import { withRehydratation, loadAndPersist } from './persist';
import enhanceWithBrowserSize from './mobile';
import applyMiddlewares from './middlewares';
import { BREAK_MEDIUM } from './constants';
+import { getEditedPostTitle } from './selectors';
/**
* Module Constants
*/
const STORAGE_KEY = `GUTENBERG_PREFERENCES_${ window.userSettings.uid }`;
+const MODULE_KEY = 'core/editor';
const store = applyMiddlewares(
registerReducer( 'core/editor', withRehydratation( reducer, 'preferences' ) )
@@ -24,4 +26,6 @@ const store = applyMiddlewares(
loadAndPersist( store, 'preferences', STORAGE_KEY, PREFERENCES_DEFAULTS );
enhanceWithBrowserSize( store, BREAK_MEDIUM );
+registerSelectors( MODULE_KEY, { getEditedPostTitle } );
+
export default store;
diff --git a/lib/client-assets.php b/lib/client-assets.php
index a4f787ade8954..240d5793d34e3 100644
--- a/lib/client-assets.php
+++ b/lib/client-assets.php
@@ -79,7 +79,7 @@ function gutenberg_register_scripts_and_styles() {
wp_register_script(
'wp-data',
gutenberg_url( 'data/build/index.js' ),
- array(),
+ array( 'wp-element' ),
filemtime( gutenberg_dir_path() . 'data/build/index.js' )
);
wp_register_script(