Skip to content
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

Allow multiple InnerBlocks per block #6808

Closed
t-wright opened this issue May 17, 2018 · 44 comments
Closed

Allow multiple InnerBlocks per block #6808

t-wright opened this issue May 17, 2018 · 44 comments

Comments

@t-wright
Copy link

Currently InnerBlocks can only be rendered once into any block.

I can see there's been a lot of discussion around nested blocks so I'm sure there's a valid reason for this limitation but would be great to be able to be able to render multiple per block. The specific use case I'm thinking about is for a tabbed/accordion block with multiple content panes. Within each content pane you may want to have images or embeds etc.

This would be addressed in part by the inline blocks being discussed #2043 but allowing multiple InnerBlocks would provide more flexibility.

@ZebulanStanphill
Copy link
Member

@t-wright An alternative solution to the accordion block having multiple <InnerBlocks/> would be to have an accordion block with a single <InnerBlocks/> which only allows an accordion item block, which would have its own <InnerBlocks/> that allows any block to be inserted into it. One neat bonus of having each accordion item being a separate block is that you get the drag-and-drop reordering of blocks for free, plus the ability to have different styles for each accordion item.

There is one caveat, though, and it is that the UI might be a bit tricky for something like a tabbed block. An accordion block would be easy to implement with the standard block UI, but a tabbed block sounds like something that may require special coding.

So in the case of a tabbed container block, perhaps there is a need for the ability to have multiple <InnerBlocks/> after all. This also raises the question of how/if the block nesting UI should adapt for things that do not list vertically.

@ZebulanStanphill
Copy link
Member

Actually, now that I think about it, the tabbed block could probably be handled by having the block look different depending on whether or not it is selected. If unselected, the block would look pretty much like it does on the front-end, but when selected, each tab would appear as a block, all stacked vertically (like in any other container block). That is of course not the most ideal situation as it makes the block look different while it is being edited, but it does show that it is technically possible to have such a block in Gutenberg already.

@t-wright
Copy link
Author

Thanks for giving this one some thought @SuperGeniusZeb. That's an approach I hadn't thought of and I think it makes sense. And getting re-ordering for free is a massive benefit too! I'll have a crack and see how I go. Ultimately I think the API and UI could be a more intuitive if multiple <InnerBlocks /> were permitted but I think the approach you've suggested will work for my current requirements. Thanks again.

@mtias
Copy link
Member

mtias commented May 21, 2018

Multiple inner blocks have complexities that don't seem worth the effort, and in general it either hints at a block that should be broken down into multiple simpler blocks, or with block attributes (not children). Let us know how the above suggestion work for you and we could revisit.

@mtias mtias closed this as completed May 21, 2018
@emfluenceindia
Copy link

I am late on this topic but like to share one use case where multiple <InnerBlocks /> might be useful. I am building a block where front end renders two columns and it is not fixed what would go in each column. If multiple <InnerBlocks /> is supported with a sort of unique key and <InnerBlocks.Content /> can be rendered using the key it would be easier to build such blocks. But right now there is no option to create it. Native colummn block has this support but in this particular scenario that cannot be used since the columns need to have separate classes. How would one accomplish this?

@t-wright
Copy link
Author

@emfluenceindia the way I've approached a similar problem is to create my own parent and child blocks. The parent block renders an <InnerBlocks /> components which accepts the child block as a nested block.

The child block has an <InnerBlocks /> which you could wrap in a div. You could set the class on the div directly or in my case, I pass a value to a customClasses attribute on my child block to set the class dynamically. Check the docs for more on this - https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-templates/

<InnerBlocks
  allowedBlocks={ [ 'cgb/column' ] }
  template={[
    [ 'cgb/column', {
      customClasses: 'is-active',
    } ],
  ]}
/>

@rodrigowbazevedo
Copy link

I know probably there's some complexity to implement it, I think it can use the concept of named slots like all major javascript frameworks have.
Sometimes is a pain in the neck to split in unnecessary blocks just to have this behavior...

@theaceofthespade
Copy link

What would it take to get this issue opened back up? It seems like the dual answers of "it's too difficult for us to pull off" and "block developers can figure out workarounds with a combination of our template system and creating additional-sub blocks that serve no other purpose" are, at least on some level, a bit at odds with each-other. The use cases for multiple Inner Block sections are virtually endless.

@ZebulanStanphill
Copy link
Member

It's worth noting that some experimental enhancements to the InnerBlocks API have been made in #24232, allowing the children blocks to each be wrapped with a given set of markup, for example:

<InnerBlocks
	__experimentalItemCallback={ ( item ) => (
		<>
			<section>{ item }</section>
			<p>You can do cool/weird stuff like this now.</p>
		</>
	) }
/>

This is great for list-style blocks that want to wrap each child in a <li> (or any other element or elements), or for adding markup before and after each child.

With these new API additions in mind, does anyone still think multiple InnerBlocks areas are necessary? It's worth noting that conceptually, the InnerBlocks area represents the children of a block. DOM nodes only have one set of children, and right now, blocks can only have one set of children.

Allowing blocks to have multiple InnerBlocks areas would be... weird. It would make the logic in Select/Navigation mode a lot more convoluted, it would require rewrites to the List View tool, and it would drastically alter how block children are treated throughout the code. When you go to insert a block inside one of the areas, the inserter can no longer check the block's "allowed children" list, because there could now potentially be a different one for each InnerBlocks area. The InnerBlocks = "the block children" metaphor would suddenly break, and a lot of simple code would have to become noticeably more complex... and for what benefit?

Ultimately, what's wrong with using sub-blocks with their own InnerBlocks areas? It encapsulates logic and automatically solves the aforementioned Select mode and List View problems automatically. If there's any problem to solve here, it would be making it easier to traverse up and down the block hierarchy. (I'm actually working on this in #23800, though I've run into a bit of a roadblock.)

If you have multiple content areas in a block, then conceptually, each of those areas are already a sort of "block" of content. So why not just actually use blocks for them? From a code quality perspective, that seems a lot better to me. Each sub-block would already have access to the usual APIs for allowed-children, templates, variations, and so on, because it's just a normal block. If you try to go the multiple InnerBlocks route, you end up having to reimplement all of that.

So basically, I don't think using sub-blocks is a workaround; rather, I view it as the most logical solution when you're dealing with multiple sets of children. I think having multiple InnerBlocks would be the actual hacky workaround. It forces you to complicate both conceptual design and almost every block-related API... all so you can accomplish the same thing you could already accomplish. You end up gaining so little at the cost of so much.

@mtias
Copy link
Member

mtias commented Aug 25, 2020

Agreed with @ZebulanStanphill. Multiple inner block areas add unnecessary complexity to the block API, grammar, and parsing logic and provide very little value over the existing setup. As mentioned before, intermediary blocks that are hidden from inserters can be used to handle more complex cases (like core's own Columns block) while preserving parsing unambiguity, which is an important architecture consideration for long term stability and performance. I also haven't seen concrete case articulated so far where multiple block areas would actually result in a better implementation compared to using the currently recommended mechanism. Perhaps we need more examples in the documentation that target the perceived need for multiple inner blocks?

@cngodles
Copy link

cngodles commented Nov 17, 2020

Just a simple question. How is this current accomplished with the multiple column layout block within WordPress? Just the fact that this exists seems to make me think there is a way to do it.

https://wordpress.org/support/article/columns-block/

@theaceofthespade
Copy link

"intermediary blocks that are hidden from inserters"
If this isn't "unnecessary complexity for the block API and grammar" then I don't know what is. It should be plainly obvious that nothing about that is intuitive vs nested blocks from that sentence alone, and you can't complain about a feature making the api too hard to understand when the absence of the feature also creates a whole other layer of implementation complexity for your end-users.

@rodrigowbazevedo
Copy link

Imagine something simple as this example...

{
    edit: () => {
        return (
            <section>
                <aside>
                    <InnerBlocks name="sidebar"/>
                </aside>
                <div className="content">
                    <InnerBlocks />
                </div>
            </section>
        );
    },
    save: () => {
        return (
            <section>
                <aside>
                    <InnerBlocks.Content name="sidebar"/>
                </aside>
                <div className="content">
                    <InnerBlocks.Content />
                </div>
            </section>
        );
    },
}

@cngodles
Copy link

cngodles commented Nov 17, 2020

For now I'm solving with a Block Template. Which is fine, it works. I had to override some of the standard styles, and I suppress the add-new column option with CSS. I applied a grid style to .wp-block-columns , which designates positioning for each column (> .wp-block-column:nth-of-type(1)). Works fine if the user adds extra columns, but not sure what I'm going to do if the user removes columns.

Lucky for me, this isn't a public endeavor, so I can simply ask them not to do that.

const BLOCKS_TEMPLATE = [
    ['core/columns', {}, [
        ['core/column', {}, [
            ['core/image'],
        ]],
        ['core/column', {}, [
            ['core/paragraph', { placeholder: 'Enter side content...' }],
        ]],
    ]]
];

@willrowe
Copy link

willrowe commented Feb 10, 2021

The use case that brought me here is very similar to the example that @rodrigowbazevedo presented.

Currently, if you want to have multiple blocks next to each other with the same parent element, it makes sense to have one InnerBlocks and only allow a specific child block type. This works very well for lists and list items and core/columns and core/column.

However, this does not work when the child blocks are not siblings and represent different types of content. I am currently trying to build a block which is mostly static or editable using simple fields. There are two areas in this block where I would like the user to be able to add blocks like headings, paragraphs, and lists. One is a card-like element and the other is a full width area. These two areas are separated by other elements and do not share a parent element, so the solutions offered above would not work.

Being able to target both of the areas with named InnerBlocks would allow me provide a level of customization to the user, while still being able to control the look and layout of the containing block. Other solutions to this issue would be complex and require me to create a extra, completely static, blocks instead of being able to use that same markup in one block with InnerBlocks included where the content should be dynamic, all the while still not quite accomplishing what I need.

@seothemes
Copy link

+1 for this. Would be super powerful for creating multiple versions of a single block. Use cases would be translations, A/B testing etc

@rottmann
Copy link

rottmann commented Apr 7, 2021

Please re-open this. @willrowe has perfectly described the usecase.
And @rodrigodagostino wrote a simple syntax for it.

Named slots are really needed.

@ivanjeremic
Copy link

ivanjeremic commented Jan 17, 2022

@willrowe I have currently the same issue, I'm developing a Slider and as soon as I found out that I can only have one InnerBlock the whole project stopped, we really need this asap.

I want also to be able to do this whiteout naming slots use-case:

<div>
{[1, 2, 3, 4].map((slide, index) => (
 <Slider key={slide}>
     <InnerBlocks />
 </Slider >
))}
</div>

The user of my block should be able to add in each slide any block he wants, each slide should be able to have different InnerBlocks.

@rodrigodagostino
Copy link

rodrigodagostino commented Jan 18, 2022

@ivanjeremic you could check the Post Carousel that I’ve worked on, it might be helpful. Swiper is implemented behind the scenes, on the admin side as well as on the front-end side.

@ivanjeremic
Copy link

@ivanjeremic you could check the Post Carousel that I’ve worked on, it might be helpful. Swiper is implemented behind the scenes, on the admin side as well as on the front-end side.

This isn't really showing how to let the user add InnerBlocks on each slide.

@mtias
Copy link
Member

mtias commented Jan 26, 2022

For the slider case, I'd suggest treating every "Slide" as a child block, restricted to your Slideshow parent, and each Slide containing a single inner blocks.

@ivanjeremic
Copy link

ivanjeremic commented Jan 27, 2022

For the slider case, I'd suggest treating every "Slide" as a child block, restricted to your Slideshow parent, and each Slide containing a single inner blocks.

I'm a bit confused with this because I thought InnerBlocks are child blocks how would an example of this kind of look like? Thanks.

@mtias
Copy link
Member

mtias commented Jan 27, 2022

You'd have a slider block with an inner blocks area, and then a slide block with an inner blocks area. slide would be setup so it's the only type available in slider. slider can have many slide blocks.

It's essentially how columns and column works. Imagine each slide is a single column: https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/column/block.json#L7

@RuuddM
Copy link

RuuddM commented Jan 4, 2024

You'd have a slider block with an inner blocks area, and then a slide block with an inner blocks area. slide would be setup so it's the only type available in slider. slider can have many slide blocks.

It's essentially how columns and column works. Imagine each slide is a single column: https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/column/block.json#L7

So this means for an accordion block I'd get this tree view.

  • core/columns
    • core/column
      • custom/accordion
        • custom/accordion-item
          (parent: custom/accordion)
          • custom/accordion-item-heading
            (locked, parent: custom/accordion-item, child: core/heading)
            • core/heading
              (locked)
          • custom/accordion-item-content
            (locked, parent: custom/accordion-item, child: core/paragraph etc)
            • core/paragraph
            • core/list
            • core/image
            • core/heading
            • etc...
        • custom/accordion-item
          • ...
        • custom/accordion-item
          • ...

The custom/accordion-item-heading is nothing more than a static container block without any option, and just there to separate the core/heading from the other content. Same for the custom/accordion-item-content, but the other way around.

This is in my experience a bit abusive to the tree view in the editor, but works fine besides that.

@mtias
Copy link
Member

mtias commented Feb 8, 2024

@RuuddM not clear to me from the example why your core/heading couldn't sit directly under accordion-item and next to accordion-item-content.

@milanidavide
Copy link

milanidavide commented Jun 18, 2024

Hello Gutenberg Team,

I kindly request reconsideration for supporting multiple <InnerBlocks /> within a single block. The current limitation often requires creating unnecessary, one-off blocks that serve no purpose other than to work around this restriction. This also leads to a cluttered editor interface and adds complexity to the development process.

While the current approach makes perfect sense for blocks that require customizability (such as core/columns), many use cases do not necessitate this level of complexity. As a result:

  • Developers are forced to break down structures into multiple sub-blocks, making the process cumbersome and inefficient
  • Editors, are presented with a cluttered interface featuring blocks that offer no additional customization options, ultimately complicating their editing experience.

By supporting multiple <InnerBlocks />, we can simplify development, streamline the editor interface, and provide a more intuitive experience for editors. The UI would be less cluttered, and editors could focus on creating content without being distracted by "pointless", non-customizable blocks.

Thank you for your consideration and for your ongoing efforts to improve the platform.

@mtias
Copy link
Member

mtias commented Jun 20, 2024

Hello @milanidavide! Do you have an example to review that shows such a case? In the years since inner blocks has existed we have not run across any examples that wouldn't be better if rethought around the idea of a single inner area, whether that is through custom intermediary blocks or more recently with locked groups and patterns. Single children makes the system a lot easier to reason with, debug, traverse, maintain, and perform.

@milanidavide
Copy link

Hi Matias,

Thank you for your prompt response and for considering the request. I understand the benefits of a single inner area from a maintenance perspective, and I agree that it can simplify the system in many cases.

To illustrate the limitations of the current approach, let's consider a real-world example (see the attachment). Suppose we want to create a "reusable" section block that allows for customization throughout the site. This section consists of:

  • an optional eyebrow headline (first <InnerBlocks />)
  • a required title
  • an optional text / content area (second <InnerBlocks />)
  • a required gallery
  • a required marquee animated text
  • a flexible content area that can be replaced with different types of content, such as a video or an image etc. (third <InnerBlocks />)

We also want to retain full control over the section's design, as it may change in the future, and we'd like to propagate those changes easily throughout the site.

While we could break this down into multiple one-off blocks, create a template and lock it, I see several drawbacks with this approach:

  • it complicates the development process
  • it results in a proliferation of specific, single-use blocks that won't be used elsewhere
  • these blocks will clutter the editor interface, offering no benefits to the user since they're not customizable
  • by splitting the section into multiple blocks, we lose control over the overall design, making it difficult to re-arrange the content or make changes to the section's structure. We'd need to create new one-off blocks and update every instance throughout the site.

By supporting multiple <InnerBlocks />, we could create a single, flexible section block that allows for customization and control, while avoiding the complexity and clutter of the current approach. This section would be responsible for the required title, the required gallery, and the marquee animated text, while leaving room for customization with the three access points (optional eyebrow headline, optional text / content, and flexible content area) that could be effortlessly re-arranged in the future with a mass-update.

I'd appreciate your thoughts on this example.

multiple-innerblocks-example.mp4

@mtias
Copy link
Member

mtias commented Jun 20, 2024

Thanks for the example, super helpful! Given the structure you define, I don't think you need multiple innerblocks here, specially given you want to constrain the design.

I'd probably approach this by creating a pattern that's locked to content only, so the design and order of blocks cannot be changed at the root. I'd add the couple headline blocks you need, name them properly if needed, like Eyebrow Headline. (This will make it so that when editing it's easy to see it in the content panel on the sidebar.)

I'd also create Groups or Gallery blocks for the areas you want media. You can use the allowedBlocks property directly in the attributes of your group to restrict what blocks can be inserted. I put together the following quickly to illustrate:

image

You'd see that in the row of images you can only insert videos or images because it has the following markup:

<!-- wp:group {"allowedBlocks":["core/image","core/video"],"layout":{"type":"flex","flexWrap":"nowrap"}} -->

And from the outside you can edit but not reorder blocks:

image

If there's anything really specific to your use case you can create simple custom blocks to replace any of these parts, specially if you want more control over what should happen when they are empty. It can just be a regular pattern in that case as well, locking only the top level group instead.

With a mix of these tools I think you can express the design above, and fill in the gaps with custom blocks as needed. There's a limitation right now on synced patterns that doesn't expose Group inner area at the root if you opt-into overrides, but it's an active area of development. You could also name the images in the gallery if you want a specific number, even if they are empty, to expose the editing as well.

If you run into any limitations it'd be great to hear about them.

@milanidavide
Copy link

milanidavide commented Jun 20, 2024

This made me smile. It's gradually becoming quite powerful, and I'm really looking forward to the upcoming pattern overrides. However, I still think some concerns haven't been fully addressed.

Default blocks lack the fine-grained control needed for recreating "complex" designs, which is understandable. To overcome this, we need to break down the design into numerous single-use blocks that won't be reused elsewhere. This is a significant drawback for me as it complicates reasoning, maintenance, and largely extends development time. While it might be manageable for a one-time task, it really becomes burdensome when dealing with custom designs.

I believe blocks would be really powerful if they allowed multiple blocks within a single unit. This is a common UI pattern in components, whether they are made in React, Vue, or even PHP functions. We could achieve more complex designs with better maintainability and much greater efficiency.

I also have doubts about how patterns (with overrides) will behave when the pattern changes. If we decide to completely revamp the gallery's style, I guess all images would be lost. For instance, we currently have a carousel / grid, and let's assume we want to swap it with a different one.
With a holistic section block responsible for the gallery, we could swap the gallery since we retain the image IDs.
If we change the design in the synced pattern (with overrides), I assume the changes won't propagate unless we have a custom gallery block specifically made only for this section and update its design directly (or unless a migration mechanism is implemented in the future).

Additionally, I'm unsure if content-only editing will allow users to customize the overridable pattern (three points of access) on different pages with different blocks. It will address the intermediary blocks though, which will be hidden from the users.

Edit:

I've read some proposals related to the upcoming Synced Patterns Overrides and there seems to be a lot of overlap:

I would say that, while Synced Patterns Overrides and these proposals partially address my concerns, they still leave out some important ones.

@theaceofthespade
Copy link

Why are you all so stuck on this single inner block thing? The very thing that made Wordpress popular in the first place was the developers didn't have to rethink what they wanted to do. Constantly having to rethink things through this obtuse restriction is not only counter to the advantages React brings in the first place, but I can't think of a single other system that uses this approach. WHAT is the advantage?

@theaceofthespade
Copy link

"With a mix of these tools I think you can express the design above, and fill in the gaps with custom blocks as needed" Respectfully, that level of uncertainty for such a SIMPLE set of asks only further illustrates the point.

@milanidavide
Copy link

@theaceofthespade Because it's a change with significant implications, they likely gave it much more thought than we did. However, raising concerns is important so we can find common ground

@theaceofthespade
Copy link

theaceofthespade commented Jun 21, 2024

Maybe more than you did, but I've been thinking about this for years, and project after project have validated that this approach makes no sense. I would accept that if they would stop trying to feed it to everyone like it's a feature or some positive choice that they made. "We can't figure it out" is a very different answer from "no this system is so much better, no one needs it!"

So yes, if it's hard to do, they can own that and say "we're not capable" instead of trying to act like it's some ridiculous higher path or philosophical choice, because currently as far and wide as I search, there is no justification.

@theaceofthespade
Copy link

theaceofthespade commented Jun 21, 2024

@theaceofthespade Because it's a change with significant implications

Yeah development is hard sometimes. That's not the customers problem.

@mtias
Copy link
Member

mtias commented Jun 21, 2024

@milanidavide ah, yes, I think we should split the conversation about custom blocks (because core blocks may not be sufficient to your case) from the single inner block area restriction. If there are specific things that you would have used a core block for but couldn't because it wasn't flexible, please, raise it as an issue and we can look into it. There's a balance between keeping core blocks simple and powerful for extenders.

Even in your example about React children are not quite simple. It's one of the reasons why React supports fragments so you can express multiple children at the root. But notice it's still using a fragment in the tree to express it. It's not disimilar, in concept, to creating a mostly invisible custom block that doesn't show up in the inserter. Perhaps there needs to be a way to also make them invisible in the list view tree.

Pattern overrides are designed precisely to propagate given the user content is stored outside the pattern, so as long as you are mapping the right names and attributes you can change the whole design. Give it ago if you can and if you run into complications, please, open issues!

Not sure I follow the "three points of access" case, but they are overall created to improve upon the separation of content and presentation, so you can reuse designs across multiple pages and have localized content. How much is "content" is an ongoing process—for example, exposing more attributes of more blocks for editing.

@theaceofthespade saying "no" to things is as much a part of the design process, but it's not a dogmatic refusal if through feedback and articulating use cases the stance changes. Single child support as direct nodes is a very common design pattern, not sure what you mean with it being counter given that React precisely only supports a single props.children.

@theaceofthespade
Copy link

theaceofthespade commented Jun 21, 2024

@mtias
" but it's not a dogmatic refusal if through feedback and articulating use cases the stance changes"
Except that these are all valid use-cases of it, so that's not happening at all here - being able to do things another (much more confusing and obtuse way) doesn't change that - you're just ignoring them, and how MANY developers have directly told you they would intuitively solve the issue. It is dogmatic because again, there is not actually a reason for it in sight. If it's too hard to figure out then just say that.

"Single child support as direct nodes is a very common design pattern, not sure what you mean with it being counter given that React precisely only supports a single props.children"
It is OUTRAGIOUSLY disingenuous to compare React's children prop (which also accepts multiple components, or fragments, or arrays) and your incredibly restrictive single innerblock component restriction. Just look at this example that ANYONE who has used react would expect to be able to do from this very thread:

        <section>
            <aside>
                <InnerBlocks.Content name="sidebar"/>
            </aside>
            <div className="content">
                <InnerBlocks.Content />
            </div>
        </section>

That's an absolute no-brainer in react with maybe a sprinkle of context or something at worst. But in Gutenberg it literally just doesn't work. Even pointing out that React offers fragments and similar as workarounds for this sort of problem should indicate to you that that's the direction to go. I'm not even just being sarcastic - I don't even know how you made it so that you can't have two of those and it feels like a code smell to me

EDIT: Please note that I assume it is not, as I am not trying to insult you but rather communicate how counter that pattern feels to flux/react.

@theaceofthespade
Copy link

theaceofthespade commented Jun 21, 2024

@mtias Just to further emphasize this point about "not sure what you mean with it being counter given that React precisely only supports a single props.children." - If I put two inner blocks into a component, THEY'LL BOTH COME THROUGH THE CHILDREN PROP JUST FREAKING FINE.

Now to meet you half way,
" It's not disimilar, in concept, to creating a mostly invisible custom block that doesn't show up in the inserter. "
Perhaps if the workaround were as trivial to implement as a React fragment, that would get us most of the way there - and further to the point there isn't really a negative consequence to using fragments in regard to any tree structure (even if the same thing does happen behind the scenes, that node tree just isn't interacted with).

And please note, all caps should be read as emphasis/or, if you prefer, Seinfeld like Larry David yelling.

@theaceofthespade
Copy link

theaceofthespade commented Jun 21, 2024

I do sincerely apologize for the broken up responses but I'm trying to provide more useful feedback as I get opportunities.
It feels like an antipattern that there is functionality available to the json that is not accessible from the react. I believe the reason many find it counterintuitive (myself certainly included) is that it starts to feel like the nesting bounces back and forth between block-template json structures and react components, rather than simply being able to build out the designs/templates in react - that's what it seems like the promise is.

It would make sense for the block templates/json to enforce structure rules that would be difficult to do in react (not that that isn't the case here), but as it is, things that would normally be possible to do in react require the use of a json-based templating language. It creates a huge limitation on the composing power of react components-as-template-parts.

An actual technical reason
I think it's a direct violation of FLUX and, resultantly, react's best practices for two of the same child component not to work idempotently bare minimum. You're also arguably forcing state (admittedly we could probably argue whether that's state all day) further down the composition tree instead of up, the exact opposite direction it's supposed to go.

@milanidavide
Copy link

milanidavide commented Jun 21, 2024

Hello @mtias!

Core blocks are great, personally, I believe they strike a good balance between power and complexity for non-technical users.

I've been thinking about the upcoming synced patterns and the issues that I've linked in my previous answer:

  • The ability to have overridable group blocks would be exactly what we're aiming for with <InnerBlocks />
  • Theme Synced Patterns would allow developers / agencies to manage entire sections of a site via code
  • Introducing a generic "element" block that acts as a bridge between blocks and HTML elements would eliminate the need to create intermediate non-customizable custom blocks.

Here's a rough sketch of a theme-synced pattern to illustrate these ideas:

<!-- wp:element {"tag": "section", "className": "custom-section", "data-animate": "fade"} -->
    <!-- wp:element {"tag": "div", "className": "custom-section__col-content", "data-animate": "fade-up"} -->
        <!-- wp:group {"allowedBlocks": ["core/heading"], "metadata": {..., "name": "Eyebrow Headline"}} -->
            ... (optional headline)
        <!-- /wp:group -->

        <!-- wp:heading {"fontSize": "x-large", ...} -->
            ... (default title)
        <!-- /wp:heading -->

        <!-- wp:element {"tag": "div", "className": "custom-section__content", "data-animate": "fade-up"} -->
            <!-- wp:group {"metadata": {"name": "Content"}} -->
                ... (optional content)
            <!-- /wp:group -->
        <!-- /wp:element -->
    <!-- /wp:element -->

    <!-- wp:element {"tag": "div", "className": "section__col-media", "data-animate": "fade-left"} -->
        <!-- wp:group {"allowedBlocks": [...], "metadata": {..., "name": "..."}} -->
            ... (default block)
        <!-- /wp:group -->
        ...
    <!-- /wp:element -->
<!-- /wp:element -->

Hopefully, this is not gibberish as I haven't had much opportunity to play around with patterns yet.

I guess it's even possible to swap one block for another in the pattern (e.g. replace a gallery with another gallery) and maintain all the overrides since the binding is generic.

You would still need some custom one-off blocks for specific features, such as the marquee animated text from the initial example, which would only work within this pattern. To better explain what I had in mind with multiple <InnerBlocks />, here's the translation of the previously sketched pattern:

<section className="custom-section" data-animate="fade">
    <div className="custom-section__col-content" data-animate="fade-up">
        {/* Optional headline */}
        <InnerBlocks slot="eyebrow-headline" name="Eyebrow Headline" />

        {/* Default title */}
        <RichText value="..." />
	
        <div className="custom-section__content" data-animate="fade-up">
            {/* Optional content */}
            <InnerBlocks slot="content" name="Content" />
        </div>
    </div>

    <div className="custom-section__col-media" data-animate="fade-left">
        <InnerBlocks slot="..." name="..." />
        {/* ... */}
    </div>
</section>

As for the title, a block like this one could also be responsible for the animated marquee text or other required parts.

The editing experience could look something like this:

ui

@mtias
Copy link
Member

mtias commented Jun 22, 2024

@theaceofthespade I think you are confusing composition in general with children prop specifically. You are saying you don't want to use intermediary custom blocks for composition and that you want to leverage inner blocks multiple times directly. Inner blocks, however, functions like prop.children, where you only access a single prop. The point being that the children are not managed directly by your component but passed through. It is the same story with blocks, with inner blocks being independent nodes in the block tree. You can have as many group block in your single inner blocks as you need to. In fact, you don't need to use fragments for that! But each block can only contain a single inner blocks prop, the same way React components can only have a single children prop. You can attach multiple children to that prop, same way you can attach multiple blocks to your inner blocks area. All the use cases described have not pointed yet to a case where you don't want wrapper nodes for the separate inner block areas, which would make the boundaries indistinguishable to parse between those areas.

@milanidavide your wp:element suggestion is similar to an idea explored a few years ago with "transparent" blocks acting as wrapping nodes for the parser and block tree structure but being non visible and non interactive to the user. I think it's more likely now that group becomes that utility, which is what #61225 describes in part. There are intricacies in the user experience for invisible nodes—moving, copying, dragging, etc. In your example, the conceptual structure seems to be a single inner block area where you are passing a block tree made of a few custom blocks and some groups. Better ergonomics for passing a template like that to inner blocks would be good to improve.

@theaceofthespade
Copy link

theaceofthespade commented Jun 25, 2024

@mtias I think you're just misunderstanding separate concerns (totally my fault).

"You are saying you don't want to use intermediary custom blocks for composition and that you want to leverage inner blocks multiple times directly. Inner blocks, however, functions like prop.children, where you only access a single prop"

If I'm not mistaken we're just talking about the same thing from opposite directions here. I'm referring to the fact that the child prop receives multiple things from its parent through the child prop, whereas you're referring to the fact that each component only receives a single child (which is of course true).

So to reiterate this comparison, the difference between passing things via the children prop in react vs any other component prop are pretty trivial - I am referring to the fact that what goes into the children prop is not restricted or particularly unique.

"The point being that the children are not managed directly by your component but passed through."

This makes no sense, because the component decides where the children are going. With multiple inner blocks the component would just be deciding (via whatever means) where multiple "children" are going.

I would also argue that this is forcing state down into child components instead of lifting it up the tree the way React intends for you to do.

"But each block can only contain a single inner blocks prop, the same way React components can only have a single children prop."

But the prop is not the problem; it's the restrictions that the child components put on the composition of the parent components. A key component of that children implementation in React is that the child components don't care what their parent is as long as they are fed the correct parameters, so that you can put whatever you want into the children prop (or any other prop). In terms of functional programming, you're ideally pushing toward pure functional components, and it is arguably an antipattern that using the same component twice in a row causes different outcomes.

"All the use cases described have not pointed yet to a case where you don't want wrapper nodes for the separate inner block areas, which would make the boundaries indistinguishable to parse between those areas."

This attitude is a problem - YOU don't get to tell me what I want, or what any of these other developers want. Or, rather, you can, but you need to listen when I tell you you're WRONG. This is quite literally you plugging your ears to complaints.

I want to be able to build my layouts without thinking about it, placing components idempotently the way one quite literally does with all other react libraries, and I was never worried that it "would make the boundaries indistinguishable to parse between those areas" because that should be the case regardless.

@mtias
Copy link
Member

mtias commented Aug 3, 2024

Sorry missed your follow up @theaceofthespade.

I'm not sure if we are talking past each other, but just typing someone is WRONG in all caps is not the most eloquent argumentation path. You talk interchangeably about children being multiple things and I'm trying to say that it represents a single tree, regardless of where you place it.

The way I interpret the requests are about declaring a transparent wrapper like #7694 (akin to react fragments) to avoid spurious container just to hold other blocks, not to multiple inner blocks / children props which is a whole different ordeal. That transparent wrapper is easy to conceptualize in the architecture of blocks, but it's not clear what the user experience should be as users navigate the block hierarchy or move things around. So that also needs an answer.

@theaceofthespade
Copy link

Sorry missed your follow up @theaceofthespade.

How dare you, what else could you possibly have to do than talk to me, a relative stranger with virtually no stake?!

I actually recently realized that other people do not see all caps as benign as I do, a mistake on my part (minus more aggressive language, i have always read it as a sort of emphasis rather than anything meant to intimidate or express anger, mostly because of the impotence of written communication).

BUT in fairness, that "WRONG" was in the middle of a paragraph where I felt I was explaining why I thought you were wrong, and the emphasis was that unlike most of this, i believe this part is objective: you simply are not in a position to tell users what they do and do not want. You can at best suggest that you don't feel like that feature serves their expressed interests, but at the end of the day when a user tells you "that's not what I want," your opinion on the validity of that statement is entirely irrelevant. Presuming to tell users what they want is a great way to never hear it out (all that being said, I also understand that you're sometimes being put in a position to defend decisions, so I'm not trying to make blanket statements about this multi year thread).

In a similar way, I think we are repeatedly branching the react children analogy conversation. I was making an initial point that I still do not believe is being fully understood, but also tried to address your disagreements (which is what i think you're picking up on in regard to me speaking interchangeably). Ie my point is not that your child prop assertions are necessarily WRONG (I couldn't resist), but rather that they are irrelevant to the point I was originally trying to communicate.

So to emphasize my emphasis: in most react libraries and patterns I'm aware of, this sort of composition restriction very rarely exists, and the only examples I can think of are a direct result of some sort of pre-existing technical limitation (eg a wrapper for a dom manip library that only works on a page once, or certain meta tag react implementations). So I would reiterate that my intent was not to compare and contrast child prop implementations (not that i didn't do that), merely to communicate about this specific, nuanced dev-ex difference.

I think 7694 potentially does address some of these if my initial read is correct (i'd take it over nothing) but I respectfully think you're still over thinking the request (game recognize game). Even before knowing exactly what the state access procedure would be, people expect to be able to freely compose their react components. Developers are coming to the "build in react" sign and expecting to be able to do all of it in react, the same way they would build anything else. I know because I've watched 4 different seasoned devs have the same experience on our team, and QUICKLY lose a lot of excitement for switching over.

I believe people like myself are tripped up on this because they aren't used to working around this particular pattern/type of restriction on components, and it feels like Gutenberg limiting react instead of supercharging or extending it. I would go so far as to say it breaks the promise of letting you just build in react, especially when you must use a separate json file to accomplish things that feel (emphasis on feel) like they should be possible with a react library by itself.

When you have a chance, if you could expound on those access concerns I would be happy to brainstorm/ try and suggest solutions. I am not implying I have all the answers or anything, merely that I can be more constructive than just complaining/criticizing if there are questions that need answers like that (even if only in a monkeys on typewriters sort of way).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests