Properly resolve getTemplateId for hybrid themes#76532
Conversation
getTemplateId for hybrid themesgetTemplateId for hybrid themes
|
Size Change: +16 B (0%) Total Size: 7.66 MB
ℹ️ View Unchanged
|
|
Flaky tests detected in 08982f1. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/23427105015
|
| ( select ) => ( state, postType, postId ) => { | ||
| const homepage = unlock( select( STORE_NAME ) ).getHomePage(); | ||
|
|
||
| if ( ! homepage ) { |
There was a problem hiding this comment.
Removing this check might result in the wrong template being returned on block themes for a split second before the home page actually loads, so maybe there's a way to keep this check for block themes somehow?
There was a problem hiding this comment.
Yes, if the homepage is not yet resolved, but exists, the selector may briefly return a wrong result. Instead of returning the front-page template, it will returns something else, like the default template for post pages etc.
Interesting that there are two other usages of getHomeTemplate where there is the same issue: useDefaultTemplateLabel and getDefaultTemplateLabel. All three functions have a copy&paste of the same logic, but only one returns early when homepage is null.
I think what we really want is that getHomePage has different return values for "still resolving" and "there is no homepage". In getHomePage we can achieve that by checking the resolution status of getDefaultTemplateId:
const query = { slug: 'front-page' };
const frontPageTemplateId = select( STORE_NAME ).getDefaultTemplateId( query );
if ( frontPageTemplateId ) {
return { postType: 'wp_template', postId: frontPageTemplateId };
} else if ( select( STORE_NAME ).hasFinishedResolution( 'getDefaultTemplateId', [ query ] ) ) {
return {}; // resolution finished and yet no template: empty homepage
} else {
return null; // still resolving
}After this, callers of getHomePage can propertly branch on whether the home page is still loading, or is definitely empty.
There was a problem hiding this comment.
Yeah, I thought it might fine to have this brief change, as I describe it in the PR, but it might be better to follow the quite common pattern for selectors to return null while resolving.
I'm still not sure about my updates. I'll resolve this discussion since I removed the code and comment on the new code.
There was a problem hiding this comment.
Yes, changing the return value of getDefaultTemplateId to '' vs null also works. We're slightly changing behavior of a public selector, I'm not sure how serious is it from back-compat perspective.
It would be also nice to modify useDefaultTemplateLabel and getDefaultTemplateLabel so that the behavior of all three functions is exactly the same. It's a pity that the fields package duplicates a complex logic from core-data -- maybe we need to create or reuse a selector that contains that logic just once?
There was a problem hiding this comment.
I'm not sure how serious is it from back-compat perspective.
I'm not sure about that.. It's still a falsy value, but consumers could check for undefined explicitly? 🤔
For now I'll remove the template field utils (getDefaultTemplateLabel) since it seems they are not used.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
be1112a to
e02a7cb
Compare
| } | ||
| return { postType: 'wp_template', postId: frontPageTemplateId }; | ||
| // Resolution is finished and no front-page template exists. | ||
| if ( frontPageTemplateId === '' ) { |
There was a problem hiding this comment.
This might be related to the comments below about cache invalidation.
I couldn't use hasFinishedResolution (always false) because probably it's affected by createSelector and the lack of access to state.metadata (where hasFinishedResolution is included).
That's the reason for checking for empty string and also setting an empty string to getDefaultTemplateId when the request has resolved (see).
Lot's of interconnected complex pieces and I'm not 100% confident about the fix.
There was a problem hiding this comment.
You mean, you can't call hasFinishedResolution for getDefaultTemplateId, it returns bad results? That's strange. It would likely be a bug in the select function we're passing to selectors, maybe the privateness of the selector is also relevant.
We certainly can't call hasFinishedResolution for getTemplateId, because it's a selector that doesn't have a resolver. It only combines data from other resolver-enabled sub-selectors. Here it's expected that the return value is always false.
There was a problem hiding this comment.
hasFinishedResolution for getDefaultTemplateId seems to be always false. If we don't use createSelector it works just fine.
There was a problem hiding this comment.
I debugged the hasFinishedResolution mystery. What happens is:
- after the
getDefaultTemplateIdresolver sets the new ID in the state, all store listeners run synchronously, before thedispatchcall returns. ThegetHomePageselector runs again because it's triggered by the listener. - but the
finishResolutionaction is dispatched only a little later. Now it triggers thegetHomePageselector once more, but because the resolution state is not part of thecreateSelectordeps, and probably can't even be, the value is not recomputed. The old cached return value is returned again.
So, hasFinishedResolution is not always false, it becomes true, but at that time it's too late.
There is not even any timeout between the two dispatches, the code is similar to:
dispatch( receiveDefaultTemplateId() );
dispatch( finishResolution() );It's all synchronous and very close to each other. But still late.
| if ( id ) { | ||
| template.id = id; | ||
| registry.batch( () => { | ||
| dispatch.receiveDefaultTemplateId( query, id ); |
There was a problem hiding this comment.
Can we keep all the dispatches in one batch? If we don't there is this code in getTemplateId:
const homepage = select( STORE_NAME ).getHomePage();
const templates = select( STORE_NAME ).getEntityRecords( 'postType', 'wp_template' );The receiveDefaultTemplateId, dispatch sets a valid homepage, but then the getEntityRecords selector will be called before the receiveEntityRecords dispatch ran. That could cause an inefficiency. One extra selector call at best, an extra REST request at worst.
jsnajdr
left a comment
There was a problem hiding this comment.
Looks good, this is the best we can do at the moment.
I posted one suggestion to keep dispatches in one batch, as they were before, just to be sure we're not causing any inefficiency or even bug.
112e8d3 to
08982f1
Compare
|
I think it's fine to land, especially now after the issue with It's not a great solution, but it's not the PR's fault, it's an issue with the underlying |
What?
While working on #76104 I observed that hybrid themes (classic themes with template support) would never show the
edit templatebutton, even if a page was assigned to a block theme.Unless I'm missing something this has regressed for years now from: #67031. -cc @youknowriad
In that PR it seems this check was added but mostly to indicate that the resolution of internal selectors (example) hadn't finished yet.
getDefaultTemplateIdis used internally and the REST API checks only for block templates, so in hybrid themes with no bundled homepage/frontpage templates thegetTemplateIdalways returns null now, making it impossible to edit a block template in hybrid themes.I considered updating the
getHomePageselector to return an object with explicitisResolvingprop, but probably it's fine to just remove the single check.The outcome for block themes is that in the case of a post which is assigned as
homepage/frontpagewill transition frompage's template->homepage/frontpage, while now just transitions fromundefined->homepage/frontpage.For hybrid themes with a block template assigned, it will return the proper template id and in the case a php template is assigned, the
templateIdwill be undefined (as is now).Testing Instructions
templateIdin the editor/site editorhomepage/frontpageto a specific page from the settings and check that the proper template is resolved.editbutton is shown in inspector controls for that templateI'm testing with
2014which already have some page templates and in order to add template support add the following snippet: