Skip to content
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

Data Module: Restrict the state access to the module registering the reducer only #4058

Merged
merged 1 commit into from
Dec 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,20 @@ The more WordPress UI moves to the client, the more there's a need for a central
This module holds a global state variable and exposes a "Redux-like" API containing the following methods:


### `wp.data.registerReducer( reducer: function )`
### `wp.data.registerReducer( key: string, reducer: function )`

If your module or plugin needs to store and manipulate client-side data, you'll have to register a "reducer" to do so. A reducer is a function taking the previous `state` and `action` and returns an update `state`. You can learn more about reducers on the [Redux Documentation](https://redux.js.org/docs/basics/Reducers.html)

### `wp.data.getState()`
This function takes two arguments: a `key` to identify the module (example: `myAwesomePlugin`) and the reducer function. It returns a Redux-like store object with the following methods:

A simple function to returns the JS object containing the state of all the WP Modules.
This function is present for convenience to use things like `react-redux`.
You should not use rely on other modules state since the state object's shape may change over time breaking your module.
An official way to expose your module's state will be added later.
#### `store.getState()`

### `wp.data.subscribe( listener: function )`
Returns the state object of the registered reducer.

#### `store.subscribe( listener: function )`

Registers a `listener` function called everytime the state is updated.

### `wp.data.dispatch( action: object )`
#### `store.dispatch( action: object )`

The dispatch function should be called to trigger the registered reducers function and update the state. An `action` object should be passed to this action. This action is passed to the registered reducers in addition to the previous state.
30 changes: 22 additions & 8 deletions data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,32 @@ const initialReducer = () => ( {} );
const store = createStore( initialReducer, {}, flowRight( enhancers ) );

/**
* Registers a new sub reducer to the global state
* 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} key Reducer key
* @param {Object} reducer Reducer function
*
* @return {Object} Store Object
*/
export function registerReducer( key, reducer ) {
reducers[ key ] = reducer;
store.replaceReducer( combineReducers( reducers ) );
}
const getState = () => store.getState()[ key ];

export const subscribe = store.subscribe;
return {
dispatch: store.dispatch,
subscribe( listener ) {
let previousState = getState();
const unsubscribe = store.subscribe( () => {
const newState = getState();
if ( newState !== previousState ) {
listener();
previousState = newState;
}
} );

export const dispatch = store.dispatch;

export const getState = store.getState;
return unsubscribe;
},
getState,
};
}
13 changes: 5 additions & 8 deletions data/test/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { registerReducer, getState } from '../';
import { registerReducer } from '../';

describe( 'store', () => {
it( 'Should append reducers to the state', () => {
const reducer1 = () => 'chicken';
const reducer2 = () => 'ribs';

registerReducer( 'red1', reducer1 );
expect( getState() ).toEqual( { red1: 'chicken' } );
const store = registerReducer( 'red1', reducer1 );
expect( store.getState() ).toEqual( 'chicken' );

registerReducer( 'red2', reducer2 );
expect( getState() ).toEqual( {
red1: 'chicken',
red2: 'ribs',
} );
const store2 = registerReducer( 'red2', reducer2 );
expect( store2.getState() ).toEqual( 'ribs' );
} );
} );
6 changes: 4 additions & 2 deletions editor/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ import { PREFERENCES_DEFAULTS } from './defaults';
import reducer from './reducer';
import { withRehydratation, loadAndPersist } from './persist';
import enhanceWithBrowserSize from './browser';
import store from './store';
import applyMiddlewares from './middlewares';

/**
* Module Constants
*/
const STORAGE_KEY = `GUTENBERG_PREFERENCES_${ window.userSettings.uid }`;

registerReducer( 'core/editor', withRehydratation( reducer, 'preferences' ) );
const store = applyMiddlewares(
registerReducer( 'core/editor', withRehydratation( reducer, 'preferences' ) )
);
loadAndPersist( store, 'preferences', STORAGE_KEY, PREFERENCES_DEFAULTS );
enhanceWithBrowserSize( store );

Expand Down
23 changes: 8 additions & 15 deletions editor/store/store.js → editor/store/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@
*/
import refx from 'refx';
import multi from 'redux-multi';
import { get, flowRight } from 'lodash';

/**
* WordPress dependencies
*/
import { dispatch, getState, subscribe } from '@wordpress/data';
import { flowRight } from 'lodash';

/**
* Internal dependencies
Expand All @@ -19,18 +14,17 @@ import effects from './effects';
/**
* Applies the custom middlewares used specifically in the editor module
*
* It also restricts the getState call to the module's partial state only
* @param {Object} store Store Object
*
* @return {Object} Redux custom store
* @return {Object} Update Store Object
*/
function applyMiddlewaresAndRestrictState() {
function applyMiddlewares( store ) {
const middlewares = [
mobileMiddleware,
refx( effects ),
multi,
];

const enhancedGetState = () => get( getState(), 'core/editor' );
let enhancedDispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
Expand All @@ -40,17 +34,16 @@ function applyMiddlewaresAndRestrictState() {
let chain = [];

const middlewareAPI = {
getState: enhancedGetState,
getState: store.getState,
dispatch: ( ...args ) => enhancedDispatch( ...args ),
};
chain = middlewares.map( middleware => middleware( middlewareAPI ) );
enhancedDispatch = flowRight( ...chain )( dispatch );
enhancedDispatch = flowRight( ...chain )( store.dispatch );

return {
...store,
dispatch: enhancedDispatch,
getState: enhancedGetState,
subscribe,
};
}

export default applyMiddlewaresAndRestrictState();
export default applyMiddlewares;