-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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: Adding The queried data handling into the entities abstraction #8357
Conversation
d095c69
to
0408943
Compare
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.
packages/core-data/src/actions.js
Outdated
@@ -56,13 +61,13 @@ export function addEntities( entities ) { | |||
* @param {string} kind Kind of the received entity. | |||
* @param {string} name Name of the received entity. | |||
* @param {Array|Object} records Records received. | |||
* @param {Object?} query Query Object. |
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.
Based on documentation, nullable type should be denoted with ?
as prefix, not suffix:
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.
Eslint should be more proactive 😄
|
||
/** | ||
* Returns items for a given query, or null if the items are not known. Caches | ||
* result by both per state (by reference) and per query (by deep equality). |
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.
Grammar: Remove first "by" in this line.
packages/core-data/src/selectors.js
Outdated
@@ -148,15 +149,17 @@ export function getEntityRecord( state, kind, name, key ) { | |||
* @param {Object} state State tree | |||
* @param {string} kind Entity kind. | |||
* @param {string} name Entity name. | |||
* @param {?Object} query Optional terms query. |
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.
Minor: Alignment on (a) argument name, (b) argument description.
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 left my feedback. This is looking really good. I have some concerns regarding caching strategy. I'm not quite sure what are use cases we are dealing with at the moment. However, I anticipate some issues in the future based on my experience with similar solutions I worked with in the past.
We also should start thinking how to invalidate the cache when data changes on the server and we want re-fetch it because optimistic updates are not sufficient.
* | ||
* @return {Object} Action object. | ||
*/ | ||
export function receiveQueriedItems( query = {}, items ) { |
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.
Nit: It looks like the order of the params should be reversed given that query is optional.
packages/core-data/src/actions.js
Outdated
@@ -56,13 +61,13 @@ export function addEntities( entities ) { | |||
* @param {string} kind Kind of the received entity. | |||
* @param {string} name Name of the received entity. | |||
* @param {Array|Object} records Records received. | |||
* @param {Object?} query Query Object. |
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.
Eslint should be more proactive 😄
export function getEntityRecords( state, kind, name, query ) { | ||
const queriedState = get( state.entities.data, [ kind, name ] ); | ||
if ( ! queriedState ) { | ||
return []; |
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.
Should []
be assigned to const, moved out and always return the same reference to avoid rerenders? I think createSelector
assured that in the past.
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 think this is a rare use-case that can happen only if the entities has just been registered (lazy entities).
post: { slug: 'post' }, | ||
page: { slug: 'page' }, | ||
}, | ||
queries: { | ||
'': [ 'post', 'page' ], |
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.
Is this order of items random or can we predict it?
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 think it doesn't matter after looking at implementation details.
} ); | ||
|
||
it( 'should not call reducer if predicate returns false', () => { | ||
const state = {}; |
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 would be nice to wrap with deepFreeze
/ Object.freeze
to ensure there is no mutation.
} | ||
|
||
it( 'should call reducer if predicate returns true', () => { | ||
const reducer = createEnhancedReducer( () => true ); |
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.
Isn't it edge case when state is undefined
? There should be another test for an existing state.
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 guess it's not important because the idea is just to check that the reducer was called or not, I'm updating the default state anyway :)
} ); | ||
|
||
it( 'should ignore actions where property not present', () => { | ||
const state = {}; |
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 would be nice to have initial state wrapped with Object.freeze
, too.
switch ( key ) { | ||
case 'page': | ||
case 'perPage': | ||
parts[ key ] = value; |
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.
Should value
be mapped to string or number to ensure it has the same type when passing to the cache no matter what plugin developer provided as a value? Knowing the dynamic
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( 'encodes stable string key with page normalized', () => {
const first = getQueryParts( { '?': '&', b: 2, page: 1 } );
const second = getQueryParts( { b: 2, '?': '&', page: '1' } );
expect( first ).toEqual( second );
expect( first ).toEqual( {
page: 1,
perPage: 10,
stableKey: '%3F=%26&b=2',
} );
} );
This fails.
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.
Not certain about this, I'll defer to @aduth as he originally built this utility.
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.
Yes, it seems reasonable to Number( value )
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.
Should
value
be mapped to string or number to ensure it has the same type when passing to the cache no matter what plugin developer provided as a value?
Normalized to number as of 37e0d7a.
( parts.stableKey ? '&' : '' ) + | ||
encodeURIComponent( key ) + | ||
'=' + | ||
encodeURIComponent( value ) |
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.
Does it assume that value
can by only represented by primitive types?
Otherwise, we should add deep sorting for arrays or objects and serialization mechanism.
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.
Example test case:
it( 'encodes stable string key when using arrays', () => {
const first = getQueryParts( { '?': '&', b: [ 3, 2 ] } );
const second = getQueryParts( { b: [ 2, 3 ], '?': '&' } );
expect( first ).toEqual( second );
expect( first ).toEqual( {
page: 1,
perPage: 10,
stableKey: '%3F=%26&b=2',
} );
} );
Not sure how stable key should look like in such case.
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.
Yeah, currently I think this would only support primitive values. It's not quite so straight-forward, but I could imagine recursing into getQueryParts
for non-primitive types.
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.
Does it assume that
value
can by only represented by primitive types?
Added deep value support (via addQueryArgs
) in 0368719.
So the issue was that we were considering a single entity record fetch as a query fetch because we're calling I had two options to fix it:
|
On a glance, this is the exact use-case for why More to come as I evaluate more deeply.. |
I pushed 803b12e as my preferred alternative. I think this could be taken further to a separate |
@aduth I'm fine either way, I just thought that the fetch one request is just a "special" query but I don't have strong arguments. |
…action Co-authored-by: aduth <[email protected]>
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.
There is one more question that needs to be answered before we proceed: https://github.com/WordPress/gutenberg/pull/8357/files#r207456393. Otherwise, it looks great. Looking forward to seeing it n use 🚀
This PR supersedes #6395
I wasn't able to rebase it because of the long history, so I just started fresh and copy/pasted code (added a co-author)
Here are the differences with the original PR:
Testing instructions