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

Refactor the core/data store to be independent from the registry #14634

Merged
merged 7 commits into from
Mar 28, 2019

Conversation

youknowriad
Copy link
Contributor

@youknowriad youknowriad commented Mar 26, 2019

Related #14369

There's a lot in this PR but I don't know if it's breakable into smaller items. The main ideas here:

  • Bake-in the "core/data" store as a reducer embedded in any namespace store
  • Build a "virtual" store added to all registries but proxify its selectors/actions per store
  • Bake the controls plugin in the namespace stores (it wasn't possible to keep it separate and bake "core/data")

This is not ready as I didn't check the tests yet.

@youknowriad youknowriad self-assigned this Mar 26, 2019
Copy link
Contributor

@nerrad nerrad left a comment

Choose a reason for hiding this comment

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

I like the direction of this.

Build a "virtual" store added to all registries but proxify its selectors/actions per store

I'm assuming this refers to the core/data generic store instantiated for each created registry. If so, I have to admit the terminology gets me tripped up sometimes because a registry can have multiple stores registered on it, so the above sentence reads better to me as "proxify its selectors/actions per registry".

Bake the controls plugin in the namespace stores (it wasn't possible to keep it separate and bake "core/data")

I think this seems sensible given the functionality of controls.

packages/data/src/namespace-store/index.js Show resolved Hide resolved
packages/data/src/store/index.js Show resolved Hide resolved
packages/data/src/store/index.js Show resolved Hide resolved
packages/data/src/registry.js Outdated Show resolved Hide resolved
@youknowriad youknowriad marked this pull request as ready for review March 26, 2019 13:11
@nerrad
Copy link
Contributor

nerrad commented Mar 26, 2019

With the controls plugin being baked in, I wonder if - in a separate pull after this lands - if we should also bake in some common controls often used. Things like:

  • apiFetch
  • select
  • dispatch
  • resolveSelect
  • resolveDispatch (for dispatches that return a value)
  • subscribe

@youknowriad
Copy link
Contributor Author

@nerrad or maybe a dedicated @wordpress/data-controls

@nerrad
Copy link
Contributor

nerrad commented Mar 26, 2019

or maybe a dedicated @wordpress/data-controls

Oh ya, even better. That's something that can be done irrespective of this landing. I might give a go at it sometime soon.

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.

I personally don't have an issue with making controls plugin default behavior. The documentation should be updated accordingly.

packages/data/src/namespace-store/index.js Outdated Show resolved Hide resolved
packages/data/src/registry.js Outdated Show resolved Hide resolved
packages/data/src/store/index.js Show resolved Hide resolved
}
let resolvers;
const actions = mapActions( {
...metadataActions,
Copy link
Member

Choose a reason for hiding this comment

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

An initial thought occurred to me that there's risk for conflict between these metadata actions and a store's own. On further consideration, I don't see it likely to occur, or at least it should be known that these "baseline" actions/selectors exist for every store. Which leads me to wonder: How do we document this?

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, I assume these can't be auto-documented. I'll see if I can add a note in the data module README (where we document registerStore)

Copy link
Member

Choose a reason for hiding this comment

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

yes, I assume these can't be auto-documented.

I'm not so overly concerned about the selectors themselves (though it would be nice), more to just general awareness that this is a behavior to expect, the fact that resolution behavior is included in every store by default.

Copy link
Contributor

@nerrad nerrad Mar 27, 2019

Choose a reason for hiding this comment

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

There's potential side-effects here of consuming code being able to "override" default metadata selectors and actions. I almost wonder if there should be some enforcement here: i.e. checking the incoming actions/selectors and ensuring there are no collisions with the corresponding metadata functions? (or maybe change the order in the constructed array so metadata always overwrites the stores own?).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we're not confident exposing these selectors and actions yet, we can easily add __internal or __experimental for now, what do you think?

Copy link
Contributor

@nerrad nerrad Mar 27, 2019

Choose a reason for hiding this comment

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

I'm less concerned with exposing them (I think they should be exposed?) and more concerned with potential conflicts.

I do think it'd be better (at least initially) to do this:

// for actions
const actions = mapActions( {
    ...options.actions,
    ...metadataActions,
} );
// and something similar with selectors reversing the order

That way the metadataActions and metadataSelectors take precedence. Of course you may intentionally have them this way so they can be overridden by the incoming store config but if not, I think this is safer.

let selectors = mapSelectors( {
...mapValues( metadataSelectors, ( selector ) => ( state, ...args ) => selector( state.metadata, ...args ) ),
...mapValues( options.selectors, ( selector ) => {
if ( selector.isRegistrySelector ) {
Copy link
Member

Choose a reason for hiding this comment

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

This logic occurs for every selector call? In addition to the overhead of mapSelectors? Should expect a negative performance impact?

Copy link
Member

Choose a reason for hiding this comment

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

I wonder if it's not unreasonable to consider/implement what we have currently as metadata and root as in-fact two separate stores, so that we don't have to override the selector call to reach into state to provide the corrected "root" state.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

to your second point, in theory, a store could be just a scalar value which would break this proposal.

to your first point, this only happens when registering the store which I guess is fine. (only run once)

Copy link
Member

Choose a reason for hiding this comment

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

to your first point, this only happens when registering the store which I guess is fine. (only run once)

Subsequent calls would still be subject to going through the ( state, ...args ) => selector( state.root, ...args ) proxying? May be small overhead; curious though given the reality of many thousands of selector calls during typical use.

@gziolo gziolo added [Package] Core data /packages/core-data [Package] Data /packages/data labels Mar 27, 2019
@youknowriad
Copy link
Contributor Author

Anything that you consider a blocker with this. I'd like to land this to unblock a few other PRs

Copy link
Contributor

@nerrad nerrad left a comment

Choose a reason for hiding this comment

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

Not a blocker, but I do think (as slim as it is) there's potential for conflict with the resolution selectors (less so actions) (see my earlier comment).

@aduth
Copy link
Member

aduth commented Mar 27, 2019

Curious why, in Redux DevTools, I see the selectors shown for metadata state. 🤔

image

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.

I wasn't able to easily test this against my aforementioned use-case since it requires a combination of this and #14369, but conceptually this seems like a good solution to the general problem.

Still curious about what I'm seeing with Redux DevTools, but preemptively approving.

@nerrad
Copy link
Contributor

nerrad commented Mar 27, 2019

Curious why, in Redux DevTools, I see the selectors shown for metadata state

Isn't that because resolution state is stored with the selector as the key (i.e no longer need to store indexed by store name)

@aduth
Copy link
Member

aduth commented Mar 27, 2019

Curious why, in Redux DevTools, I see the selectors shown for metadata state

Isn't that because resolution state is stored with the selector as the key (i.e no longer need to store indexed by store name)

Ah! I think you're right. I still see it as unnatural to have those as direct descendents of a "metadata" key. The reducer is called isResolved, but because it's the default this key isn't shown. Makes me wonder if we ought either rename metadata or have isResolved as a proper key of the metadata state.

Small naming quibble, I'm not overly concerned about it now that it's been explained.

@youknowriad
Copy link
Contributor Author

@aduth It does seem like we could add a metadata.resolution or something like that for this state. If we need more "metadata" for data stores, we can refactor this.

@youknowriad youknowriad merged commit 1e8d781 into master Mar 28, 2019
@youknowriad youknowriad deleted the refactor/core-data-per-store branch March 28, 2019 07:39
@youknowriad youknowriad added this to the 5.4 (Gutenberg) milestone Mar 29, 2019
await fulfillment.fulfill( selectorName, ...args );
fulfillment.finish( selectorName, args );
store.dispatch( metadataActions.startResolution( selectorName, args ) );
await fulfillResolver( store, mappedResolvers, selectorName, ...args );
Copy link
Member

@aduth aduth Mar 29, 2019

Choose a reason for hiding this comment

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

I seem to be running into an issue where this doesn't actually wait for the fulfillment to complete before marking the resolution as finished:

Screen Shot 2019-03-29 at 11 58 03 AM

(Note in last three actions, I'd have expected "RECEIVE_ITEMS" to have occurred prior to "FINISH_RESOLUTION")

I tried to consider how to reproduce this outside my branch, but since we wait for most all data to be available before rendering anything, it's hard to recreate even when adding a filter like add_filter( 'block_editor_preload_paths', '__return_empty_array' );. Which has me wondering if it could in-fact be an issue which had already existed prior to this change, and only became evident when performing resolution for data which hadn't been initially requested in the display of the editor (in my case, waiting to resolve getPostType( 'wp_block' ) when saving a reusable block). In my branch, since the resolution completes before the data is available, it results in an error because postType in this assignment resolves to undefined.

I'll try to clean up my local working branch to push later this afternoon, but raising now in case anything comes to mind.

Copy link
Member

@aduth aduth Mar 29, 2019

Choose a reason for hiding this comment

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

I found the issue. I'll have a pull request shortly.

Edit: #14711

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Core data /packages/core-data [Package] Data /packages/data
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants