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

Conversation

youknowriad
Copy link
Contributor

This PR updates the API of the "data" module to restrict the access to the global state.
The module registering a reducer receives a "store" object as a return value to the registerReducer call.

Coming next

  • An official way to expose a module's data to other modules.

Testing instructions

  • Test that the editor loads and saves posts properly.

@youknowriad youknowriad added the Framework Issues related to broader framework topics, especially as it relates to javascript label Dec 18, 2017
@youknowriad youknowriad self-assigned this Dec 18, 2017
@youknowriad youknowriad requested review from mcsf, gziolo and aduth December 18, 2017 09:07
@youknowriad
Copy link
Contributor Author

cc @atimmer

Copy link
Member

@aduth aduth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An official way to expose a module's data to other modules.

Do you have any ideas to share about how this looks? I would have thought one of the ideas with a global store is that the state is easily accessible from anywhere in the system.

data/index.js Outdated
export const subscribe = store.subscribe;
return {
dispatch: store.dispatch,
getState: () => get( store.getState(), key ),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is get necessary here (or anywhere we're calling getState())?

Copy link
Member

@gziolo gziolo Dec 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can introduce the following helper method:

const getState = () => get( store.getState(), key );

and use in all places inside registerReducer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's not necessary after all

@gziolo
Copy link
Member

gziolo commented Dec 18, 2017

Do you have any ideas to share about how this looks? I would have thought one of the ideas with a global store is that the state is easily accessible from anywhere in the system.

Yes, I would love to see that explained. I wanted to integrate it for coediting branch today, but I had no clue how to register local state and access it from Redux afterward. I would love to see some example with the code to better understand the idea.

data/README.md Outdated
@@ -10,17 +10,16 @@ This module holds a global state variable and exposes a "Redux-like" API contain

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 returns a Redux-like store object with the following methods:
Copy link
Member

@gziolo gziolo Dec 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wp.data.registerReducer - should it have a name passed as a first param? I guess it should. I noticed it today, when reading docs.

@atimmer
Copy link
Member

atimmer commented Dec 18, 2017

I think this PR can go in. If we desire to have the store globally available later, we can always revert this. Adding more APIs is easier than restricting. I'm assuming

An official way to expose a module's data to other modules.

will come very soon after this PR has been merged.

@youknowriad
Copy link
Contributor Author

Do you have any ideas to share about how this looks?

I have rough ideas and some POCs. And it's two separate ideas:

  • register a subtree of resolvers and provide a GraphQL API (Provider and query HoC)
  • register selectors and provide a (Provider and withSelectors HoC)

I may submit a POC tomorrow.


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

registerReducer( 'core/editor', withRehydratation( reducer, 'preferences' ) );
let store = registerReducer( 'core/editor', withRehydratation( reducer, 'preferences' ) );
store = applyMiddlewares( store );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have 3 enhancers in here?:

  • applyMiddlewares
  • loadAndPersist
  • enhanceWithBrowserSize

Can we further refactor this code to make it more obvious that we modify store instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The store instance is only modified in applyMiddlewares

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, what about having:

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

data/index.js Outdated
return {
dispatch: store.dispatch,
getState: () => get( store.getState(), key ),
subscribe( listener ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand why using store.subscribe is not enough here. When I look at Redux source code, I see that listener is executed only at the end of the dispatch call: https://github.com/reactjs/redux/blob/master/src/createStore.js#L194. This code tries to filter out all listener calls when the local state hasn't changed. It seems like a good idea, but the question is how it works in practice? Is Redux really immutable here? It might be not architected this way to speed up building new state.

Copy link
Contributor Author

@youknowriad youknowriad Dec 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more we'd have module and plugins using the "data" module, the more we'd have actions handled only by a separate reducer which would create unnecessary listener calls for most modules/plugins. I think this is a necessary performance improvement.

Not noticeable right now because we only have one registered reducer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey, I looked into combineReducers code. It seems to be smart enough to return old state when nothing has changed. See: https://github.com/reactjs/redux/blob/master/src/combineReducers.js#L118.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but in this case the state did change but only for one module.

Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change makes a lot of sense. I left some comments when trying to understand how it is supposed to work. Feel free to ignore my code refactorings proposed, if you don't like them. They helped me to understand better what is happening, but it might be an individual thing.

The only thing that needs to be updated before merging is this issues I raised in the comment for docs: https://github.com/WordPress/gutenberg/pull/4058/files#r157513975.

@youknowriad youknowriad force-pushed the update/restrict-global-state-access branch from 7b6964a to ee520c3 Compare December 20, 2017 10:32
@youknowriad youknowriad merged commit 34a13d3 into master Dec 20, 2017
@youknowriad youknowriad deleted the update/restrict-global-state-access branch December 20, 2017 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Framework Issues related to broader framework topics, especially as it relates to javascript
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants