maxence-charriere / go-app

A package to build progressive web apps with Go programming language and WebAssembly.
https://go-app.dev
MIT License
7.98k stars 369 forks source link

Support for returning multiple root nodes from Render method #833

Closed c3rtificate closed 6 months ago

c3rtificate commented 1 year ago

Hello go-app maintainers and community,

I'm currently working on a project where I need to create reusable components and layouts. In many cases, these components consist of more than one root node. The current behavior of the Render method in components only allows for returning a single root node, which has led to some challenges in implementing the design.

For instance, consider this simple example:

type FullPageLoader struct {
    app.Compo
}

func (f *FullPageLoader) Render() app.UI {
    return app.Div().Body(
        app.Div().Class("pageloader is-full"),
        app.Div().Class("infraloader is-full is-active"),
    )
}

In this example, I'm forced to wrap my two root elements within a single parent Div. While this works, it introduces an extra Div in the DOM that can complicate CSS styling, especially in more complex components.

One potential solution could be the introduction of a new method, for instance, app.Stack() or similar. This method would allow grouping multiple UI components together without adding any additional HTML markup. Here's an example of how it could be used:

type FullPageLoader struct {
    app.Compo
}

func (f *FullPageLoader) Render() app.UI {
    return app.Stack().Body(
        app.Div().Class("pageloader is-full"),
        app.Div().Class("infraloader is-full is-active"),
    )
}

In the example above, app.Stack() allows both Div components to be returned as siblings from the Render method, without the need for an additional parent Div.

If it isn't already supported (I wasn't able to find it in the current documentation), an addition like this would be extremely helpful for building reusable components and layouts.

Alternatively, allowing Render to return a slice of app.UI would provide similar functionality:

type FullPageLoader struct {
    app.Compo
}

func (f *FullPageLoader) Render() []app.UI {
    return []app.UI{
        app.Div().Class("pageloader is-full"),
        app.Div().Class("infraloader is-full is-active"),
    }
}

Other frameworks/libraries (like React.js) have faced similar issues and introduced concepts like Fragments to return multiple root nodes. Having something analogous in go-app would greatly increase the flexibility of the Render method and provide a more intuitive way to create components with multiple root nodes.

Thank you for considering this feature request.

oderwat commented 1 year ago

As far as I know, you cannot create a component that does not consist out of just one root node with Go-App because a component is a node in the DOM. I believe that this limitation can never go away. And how should a "component" that is not just "one node" actually should work anyway? I think that you can still create "fragments", but they are just functions that return []app.UI and you need to add them through a loop or by using body(...stack) in a concrete component.

GPT-4 said:

In an HTML document, the root node is the starting point of the node tree and the topmost node in the tree. The root node of an HTML document is typically the <html> element. It's where everything else - the head and body elements, and all their children - are contained.

Now, when we talk about "multiple root nodes," we're technically venturing into a territory that doesn't align with the standard HTML structure. HTML documents are designed to have only one root node for correct interpretation by browsers.

However, if you think about individual components or chunks of HTML code, you can consider each separate chunk with a single parent element as having its own root node. For example, if you're building a web application using a component-based library or framework like React, Vue, or Angular, each component's HTML structure may have its own root node. This can be thought of as a local root node relative to the component, not the overall HTML document.

Nevertheless, a well-structured full HTML document should only have one root node, the <html> element, for proper interpretation and rendering by the browser. Multiple root nodes in a single HTML document might result in parsing errors or unexpected behavior.

Not meant serious. I think we all know what you mean with multiple root nodes (which is something that can't exist by definition)

c3rtificate commented 1 year ago

Thank you for the detailed response, oderwat!

I apologize for any confusion caused by my initial wording. When I mentioned "root nodes," I didn't mean to imply multiple root nodes in the HTML sense, but rather the "root" of a component in the context of the go-app framework.

I understand that a component is essentially a node in the DOM, and traditionally, it's seen as a single entity. The concept I was trying to bring up with app.Stack() was more along the lines of a way to return multiple children from a component without additional wrapping HTML markup. This is more of a component organization perspective rather than a traditional DOM perspective.

If this concept fundamentally contradicts the design philosophy or the roadmap of the go-app framework, I completely understand. It's just that, as someone who's building layouts and reusable components, the idea of creating custom functions returning []app.UI and then looping through them or using body(...stack) feels a bit like a workaround in an otherwise very component-oriented system.

However, I respect the boundaries and principles of the framework. If having components that return multiple top-level children (via app.Stack() or a similar hypothetical function) is not feasible or desired, then I appreciate the clarification. I brought this up as food for thought, and I hope it sparks some interesting discussions, even if the conclusion is that it's not the right direction for the framework.

Thanks for taking the time to discuss this!

oderwat commented 1 year ago

Well, I am not the maintainer nor designer of Go-App and @maxence-charriere may have ideas on how to implement a stack as part of the "component". I just can't see how that could work. I doubt that it has to do with a design philosophy. It is a limitation needed to make it work. All the Context, Dispatch and UI interfaces expect just one root. I also wished that there would be a way to have something like a "component" that "throws away itself and just renders its children". So that you could make a component that returns multiple <td></td> or even multiple <tr><td>.</...</tr> elements. But if you think about it, what would then anything mean? What would it mean that the component is resized, gets an event, gets changed? What are the children? What is its "JSValue()"? And so on...

I think that implementing some reusable components implies that you create your own "stack" like "thingy" that then can be rendered by the proper components. I have quite some methods that just return []app.UI and they then get included in a Body/Range of some component.

maxence-charriere commented 1 year ago

Since a component is a node, that would introduce complexity. This is something i encountered recently when i wanted to reduce the dom size of my website but at the end I was thinking that the gain was not that worth it.

not saying that i will never do it but it is not a priority right now.