From 9603440262d7e492c63cc3bbd0aee4119585c16f Mon Sep 17 00:00:00 2001 From: Matthew Haines-Young Date: Wed, 8 Jul 2026 09:27:20 +0100 Subject: [PATCH] Fix site editor crash when query loop contains multiple post-templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getBlock() can transiently return null in the site editor when a template includes a pattern block whose inner blocks land in the name index before their full record exists in byClientId. The resulting null in the postTemplates array crashed withPostTemplateStyles and withPostTemplateInspectorControls when they accessed postTemplate.clientId. Fix: filter nulls out of the getBlock() results in withQueryLoopContextProvider so consumers always receive valid block objects. Also fixes a secondary issue: when perPage is undefined (no limit set on a post-template) the hideAfterLimit CSS rule evaluated to nth-of-type(n+NaN), an invalid selector. Suppress the rule entirely when perPage is not set — no limit means no posts should be hidden. Reproducer: open the site editor and edit any template that embeds a pattern containing a core/query block with two core/post-template children (e.g. the editors-pick-opinion pattern on an AGBI opinion archive template). Co-Authored-By: Claude Sonnet 4.6 --- src/index.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 03048c2..641db21 100644 --- a/src/index.js +++ b/src/index.js @@ -479,7 +479,17 @@ const withQueryLoopContextProvider = createHigherOrderComponent( ); } ); - return childPostTemplatesClientIds.map( getBlock ); + // Filter out any null results: getBlock() can transiently + // return null during the site editor's initial block-tree + // construction (e.g. when a pattern block's inner blocks are + // registered in the name index before their full record lands + // in byClientId). Leaving nulls in the array crashes the + // consuming HOCs (withPostTemplateStyles, + // withPostTemplateInspectorControls) when they access + // postTemplate.clientId. + return childPostTemplatesClientIds + .map( getBlock ) + .filter( Boolean ); }, [ clientId ] ); @@ -551,13 +561,17 @@ const withPostTemplateStyles = createHigherOrderComponent( ( BlockEdit ) => { ` : ''; - const hideAfterLimit = ` + // Only inject the after-limit rule when perPage is known; without it + // the expression evaluates to NaN and produces an invalid selector. + const hideAfterLimit = perPage + ? ` ${ blockSelector } :is(.wp-block-post:not([style*="display: none"])):nth-of-type(n+${ offset + perPage + 2 }) { display: none !important; } - `; + ` + : ''; // When a subsequent post-template is shown with a positive offset, the initial dropzone placeholder // and hidden preview block cause the CSS hiding to be off by one. Each post template actually contains