-
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: Support adding and updating entities #10089
Conversation
const taxonomy = getTaxonomy( slug ); | ||
const availableTerms = getEntityRecords( 'taxonomy', slug, DEFAULT_QUERY ); | ||
const availableTermsTree = buildTermsTree( availableTerms ); |
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.
We will be generating a new reference inside buildTermsTree which will probably trigger lost of unnecessary rerenders. As possible solutions, we may create a memoized selector that returns the tree already build. Or create a local memoized one instance of build terms tree, I think something like this may work:
export default compose( [
(() => {
const memoizedBuildtermsTree = memize( buildTermsTree, { maxSize: 1 } );
return withSelect( ( select, { slug } ) => {
....
} );
})()
By invaliding, does it mean all resolvers would need to query the server again to retrieve the query? |
yes, that's the idea, on the next "selector" call of these resolvers, they will fetch again. So, only the components that are still mounted. |
packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js
Show resolved
Hide resolved
See also Query Manager, where this was its second goal to the first we mimicked already by |
Would add a parameter to getEntityRecords that allows the query to be updated be an option?
We may then provide helpers, e.g: a dumb update that just adds new elements, a sorted helper that adds sorted by a given property etc... |
const taxonomy = getTaxonomy( slug ); | ||
const availableTerms = getEntityRecords( 'taxonomy', slug, DEFAULT_QUERY ); | ||
const availableTermsTree = buildTermsTree( availableTerms ); | ||
return { | ||
hasCreateAction: taxonomy ? get( getCurrentPost(), [ '_links', 'wp:action-create-' + taxonomy.rest_base ], false ) : false, | ||
hasAssignAction: taxonomy ? get( getCurrentPost(), [ '_links', 'wp:action-assign-' + taxonomy.rest_base ], false ) : false, | ||
terms: taxonomy ? select( 'core/editor' ).getEditedPostAttribute( taxonomy.rest_base ) : [], |
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.
Existing problem but we should have a constant EMPTY_TERMS = [] and use it instead of [] so we avoid generating a new reference.
@jorgefilipecosta I'm thinking ideally, this is not handled on the caller's side but on the data module side. A rought proposal: const resolver = {
fulfill: () => {},
shouldInvalidate: ( action, ...args ) => {
return action.type === 'ADD_ENTITY' && args.name === action.name;
}
} Ideally, these invalidation rules can be "composed". If a resolvers ( |
I would prefer if we did not have to make a fetch request right after adding new items. I guess a dumb adder, a sorter and a filter by property would cover 99% of the cases and maybe we can generate the updater automatically based on the query that is already being, for complex cases we would invalid and request again. |
Considering that there is nothing about resolvers that necessitates that their implementation incur a network request, I don't know that this is strictly problematic. While an additional implementation could naively destroy the local cache and trigger a follow-up refetch, I could envision some advanced resolver which has its own reconciling cache to more efficiently resolve data after an invalidation. |
Thought: Would the inverse work / be better? Where an action define (by names?) the selectors it would invalidate? |
how do you refer to partially applied selectors? say you want to invalidate all calls to a selector with a given first argument. |
With that, I guess you could not be granular at all. Still possible, but would require designing selectors to not be so generic. Leaning towards your original proposal. Did you have any worries about it? |
I'm more okay with a solution which is "wasteful" in invalidating more than it needs to than one which requires recreating querying in the client, which I feel would be impossible to have be 100% accurate (and thus lead to inconsistencies). |
I added cache invalidation using the mechanism suggested above #10089 (comment) This is lacking some unit tests but should be considered for merge soon. |
packages/data/src/store/reducer.js
Outdated
} | ||
case 'INVALIDATE_CACHE': { | ||
const nextState = new EquivalentKeyMap( state ); | ||
nextState.set( action.args, undefined ); |
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.
@aduth .delete
didn't work, probably a bug upstream in EquivalentKeyMap
packages/data/src/store/reducer.js
Outdated
const isStarting = action.type === 'START_RESOLUTION'; | ||
const nextState = new EquivalentKeyMap( state ); | ||
nextState.set( action.args, isStarting ); | ||
return nextState; | ||
} | ||
case 'INVALIDATE_CACHE': { |
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: the data reducer does not hold a cache of data per-se, it only tracks whether resolution has occurred. Therefore, maybe a better name is INVALIDATE_RESOLUTION
or INVALIDATE_IS_RESOLVED
Thanks @aduth for the updates, I also added some tests and I think this should be ready for a final review. |
11bec02
to
aea02a9
Compare
904952e
to
5c1fd39
Compare
d3799c1
to
0f3780b
Compare
@youknowriad sorry. To clarify, could returning the action value in the |
@TimothyBJacobs The workaround is to trigger an action setting a flag at the beginning of your async action and unsetting the flag and the end and use a selector to retrieve this flag. That said, I agree we need something more consistent across actions, whether it's the promise returned by I don't see it as crucial before 5.0 because there are workarounds but it's nice to have and if we get something in, that would be great. |
Gotcha. Thank you for the alternative! Agreed not necessary for 5.0, but would be nice. If something made it into the data module after API freeze, would it still be included in 5.0? Or is the deadline right around the corner. |
I think enhancements to the API are fine. Breaking changes not that welcome. But don't take my word for it :) |
As an example: This PR refactors the HierarchicalTaxonomySelector component to use the data module instead of apiFetch.
This PR is very interesting in several aspects because it shows where we fall shart currently in Async Flow in the Data module and hopefully will allow us to solve this issue.
Notes:
1- First thing to note is that it's not fully functional yet because we need to solve a fundamental problem first: The list of categories is not being refreshed once we add a new category. That's because the queried-data state doest't invalidate the query resolvers/selectors when we add new records.
Options I can think of:
Invalidate all the resolvers performing queries. The difficulty is that it's not possible to gather automatically all these resolvers. Not sure there's a technical solution with this approach.
Rewrite the queried-data selectors to avoid relying on the IDs returned by the API request but perform the filtering client-side: Doesn't seem great neither because it's not always possible to recreate the filters client side especially since this is extensible by plugins server-side.
Which is to say, I'm not certain how to move forward with this, I need some insights/help @aduth
2- This PR also shows that to create an action composed of two actions:
we need to know when the first composed action finishes and we need to know its return value (the ID created).
This is solved in this PR by using the return value of the generators. For me it seems an ok compromise and it works well. but I know this can be considered a "bad" practice