patbenatar / rbexy

A Ruby template language and component framework inspired by JSX and React
MIT License
34 stars 5 forks source link

Question: equivalent of React's Fragment component? #92

Closed bloudermilk closed 1 year ago

bloudermilk commented 1 year ago

In JSX / React you can use Fragment components to encapsulate multiple child components without rendering any wrapping DOM elements.

Examples from React docs:

render() {
  return (
    <React.Fragment> // Or simply <>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}

I'm wishing I had this right now in a #call method with multiple children where I don't want to render another parent element.

  def call
    form_for object, url: url do |form|
      content

      Form::ButtonBarComponent.new(self).render_in(self)
    end
  end

Any way to accomplish this in rbexy today? If not, would it be a welcome contribution?

bloudermilk commented 1 year ago

I'm working around this for now by concatenating the children explicitly:

 def call
    form_for object, url: url do |form|
      content + Form::ButtonBarComponent.new(self).render_in(self)
    end
  end

It works but syntactically seems a bit strange? Other than some kind of magic DSL the only (arguably) better syntax might be to return an Array like so:

 def call
    form_for object, url: url do |form|
      [
        content,
        Form::ButtonBarComponent.new(self).render_in(self),
      ]
    end
  end
patbenatar commented 1 year ago

Technically #call can return any string and that'll be inserted into the rendered HTML output, so e.g. you could do something like:

  def call
    form_for object, url: url do |form|
      [
        content,
        Form::ButtonBarComponent.new(self).render_in(self)
      ].join
    end
  end

I'd recommend making the form_for itself a component though, and using context to pass the form builder down to child components like <Form.TextField /> etc

class FormComponent
  def call
    form_for object, url: url do |form|
      create_context(:form, form)
      content
    end
  end
end

And then you can use it like so:

<Form object={...} url={...}>
  # whatever you had as children to your example component
  <Form.ButtonBar />
</Form>
patbenatar commented 1 year ago

Haha jinx on that workaround!

patbenatar commented 1 year ago

If you wanted all forms to have that bar, you could just wrap that example template above in another component like <FormWithButtons />

bloudermilk commented 1 year ago

haha nice timing

I like the composition-based approach you proposed. I'll probably go that route for now, thanks!

patbenatar commented 1 year ago

Having everything as a component makes things smoother, rather than switching between components and Rails block-based HTML builders (those are yucky when you have to concat them like above). So I'll just wrap those Rails helpers in call-components when I do need them.