-
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
Reusable blocks: prevent infinite recursion #28405
Conversation
This reverts commit c228df2.
Would your solution also handle infinite loops that involve more than one block type? (e.g. a reusable block and a template part that both include each other) Tagging @bobbingwide |
I just tested this and seems to work well! I tested with many combinations, including:
|
Size Change: +554 B (0%) Total Size: 1.39 MB
ℹ️ View Unchanged
|
Did you try?
I'd like to see these notes and test results. |
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.
This is lovely! Works as advertised and the code is clean and easy to understand.
With the exception of the !important
in the .scss
file this one looks good to me 👍
const RenderedRefsContext = createContext( [] ); | ||
|
||
export default function useNoRecursiveRenders( ref ) { | ||
const previouslyRenderedRefs = useContext( RenderedRefsContext ); |
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 a blocker for this PR. I wanted to share my thought on how to make it general enough to use with other blocks that require the same guard (Post Content or Template Part). It looks like it would be enough to rename ref
to something more generic like uniqueId
and use Set
rather than array of strings for checks to support compound values for more complex blocks.
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.
Agreed, thanks for the note!
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 looks solid, I really like useNoRecursiveRenders
hook with the accompanying tests at is could be reused with other blocks. Later (after this fix is done), we can consider moving it to the @wordpress/block-editor
package.
It looks like it only needs some polishing on UI side. Great work @mcsf 👏🏻
Addresses feedback by ntsekouras
Originally introduced in #28405 to prevent Reusable Blocks from infinitely and fatally recurring, React hook `useNoRecursiveRenders` has a place in the block-editor package so that other block types susceptible to recursion can be fixed too.
Opened #28428 as a follow-up. |
* Block Editor: Add useNoRecursiveRenders Originally introduced in #28405 to prevent Reusable Blocks from infinitely and fatally recurring, React hook `useNoRecursiveRenders` has a place in the block-editor package so that other block types susceptible to recursion can be fixed too. * Update variables to reflect uses beyond Reusable Block * useNoRecursiveRenders: Use Set instead of Array * Add JSDoc comment.
I started playing with applying the same technique for the Template Part block that is a bit more tricky to handle as it doesn't provide a unique identifier. Anyway, when going through the PHP implementation I was worried that the change proposed prevents rendering the same Reusable block multiple times in one editor. I was able to confirm that it doesn't work, see: While it works perfectly fine in the editor because it is using Context API, in PHP it uses |
#26951 solved this by unsetting the block from the static variable after the end of the render. It should be an easy fix I think. |
Thanks @gziolo for bringing this up! I'm a bit surprised that the issue wasn't caught by this test 🤔
|
Ah, disregard. The e2e test checks only the editor, not the frontend (PHP). |
Since #28405, `render_block_core_block` prevents fatal rendering loops in Reusable Blocks by aborting if a given block has already been rendered. This effectively prevents loops, but also unintentionally prevented the same reusable block from being rendered twice on the same page, even if one isn't nested in the other. This commit fixes this by explicitly forgetting a Reusable Block after it has finished rendering.
Thanks for the report! Opened a fix at #28461. |
Since #28405, `render_block_core_block` prevents fatal rendering loops in Reusable Blocks by aborting if a given block has already been rendered. This effectively prevents loops, but also unintentionally prevented the same reusable block from being rendered twice on the same page, even if one isn't nested in the other. This commit fixes this by explicitly forgetting a Reusable Block after it has finished rendering.
* Template Part: Prevent infinite recursion Based on #28405 from @mcsf. Tries to apply the same technique used for Reusable block to prevent infinite recursion when the same block is inserted into itself at any level of nesting. * Change the order of warning messages * Prevents a block from rendering itself only when the same block type * Fix styling issues in PHP file for Template Part block * Add fixes for issues discovered while testing * Ensure immutability in useNoRecursiveRenders * Rename ref to uniqueId in the test
Description
Alternative to: #27012, #26951
Fixes: #18704
In contrast with the aforementioned pull requests, this builds on the opinion that any block type capable of recursion should be responsible for its own infinite loops. In other words, it isn't up to the block editor framework to detect and prevent block loops.
On the front end. This is the simplest end of the fix. Thanks to PHP's single-pass execution, it is enough to keep a static array inside the
render_block_core_block
. If a given Reusable Blockref
is already present in the array, prevent further rendering and issue a PHP warning (E_USER_WARNING
).On the client. We resort to React context in order to identify any blocks that have already been rendered in the same render tree. The principle here is that each
ReusableBlockEdit
instance first reads and then expands the context:This behaviour is encapsulated in the
useNoRecursiveRenders
hook, for which tests are provided.Notes
useNoRecursiveRenders
is general enough that other block types. The Template Part block would be able to use it directly.block-library/src/block
because my priority is to prevent fatals reported out there in production, but once this PR is merged we can expand the fix to Template Part.useNoRecursiveRenders
'spreviouslyRenderedRefs
could be aSet
instead of anArray
.How has this been tested?
useNoRecursiveRender
Screenshots
Types of changes
Bug fix
Checklist: