Repository: ViewComponent/view_component (Ruby)

Issue:
Strange behavior with code blocks using form helpers and Slim
Hi!

I'm playing with this project for use in production but I encountered a possible bug related with passing a Slim/HTML block to Rails helper.

```ruby
# app/components/test_component.rb

class TestComponent < ActionView::Component::Base
  def initialize(form:)
    @form = form
  end

  attr_reader :form
end
```

```slim
/ app/components/test_component.html.slim

div
  = form.text_field :name
  = form.label :name do
    div.label__text
      | Nombre
```

```slim
/ app/views/tests/show.html.slim

= form_with model: @test do |form|
  /! Test Component Start
  = render TestComponent.new(form: form)
  /! Test Component End
```

Expected output:

```html
<!-- Test Component Start -->
<div>
  <input type="text" name="test[name]" id="test_name">
  <label for="test_name">
    <div class="label__text">Nombre</div>
  </label>
</div>
<!-- Test Component End -->
```

Current output:

```html
<!-- Test Component Start -->
<div>
  <input type="text" name="test[name]" id="test_name">
  <div class="label__text">Nombre</div>
  <label for="test_name">
    <div>
      <input type="text" name="test[name]" id="test_name">
      <div class="label__text">Nombre</div>
    </div><!-- Test Component End -->
  </label>
</div>
```

In normal Rails the block of code passed to the helper works perfectly. Any ideas? Thanks!
turbo_frame_tag inside a component duplicates it
Ok, so I'm not sure if the title makes sense. Hopefully the code will.

When rendering a component which has a `turbo_frame_tag` inside it, ViewComponent (or Turbo?) will render the contents twice.
Once inside a `turbo_frame_tag` as expected, and again, with whatever's inside the `turbo_frame_tag`

Note: The content of the component html doesn't matter from my testing. I initially thought it might be another turbo frame tag or component conflicting, since my VC is quite complex.

```
- achievements.each do |achievement|
  = turbo_frame_tag(achievement.event) do
    = render(Provider::TrophyComponent.new(trophy: achievement))
```
```
= turbo_frame_tag(@trophy) do

  .cell-auto.card.--with-image data-controller='provider--trophy-component--trophy-component' id=dom_id(@trophy)
    .row.items-stretch
      .cell-auto.card class="#{ @trophy.cover.attached? ? '' : 'animate-pulse' } #{ @trophy.is_a?(XBOX::Trophy) ? 'w-60' : 'w-38' }"
        = helpers.lazy_image_tag(@trophy.cover, class: 'w-full h-full rounded-md object-cover') if @trophy.cover.attached?
```
<img width="1420" alt="Screenshot 2021-10-19 at 13 27 20" src="https://user-images.githubusercontent.com/894272/137909459-797480d3-0928-45e6-b588-d7c79f68589e.png">

To fix this, I have to extract the component inside a rails partial and wrap it inside a turbo_frame_tag in there, instead of the VC.

```
- achievements.each do |achievement|

  = turbo_frame_tag(achievement.event) do
    = render(partial: 'fix', locals: { achievement: achievement }, cache: false)
```
```
= turbo_frame_tag(achievement.event) do
  = render(Provider::TrophyComponent.new(trophy: achievement))
```
### Expected behavior
The ViewComponent shouldn't render the contents twice.

### System configuration
**Rails version**:
ruby -Rails 6.1.4.1
**Ruby version**:
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin20]
**Gem version**:
  view_component (2.40.0)
  turbo-rails (7.1.0)
`turbo_stream` helpers not capturing correctly
I'm trying to update multiple parts of the page by rendering a component that uses multiple `turbo_stream.update` tags. This comes out mangled, with a subset of the expected HTML rendered followed by several empty `turbo-stream` tags. I'll put together a failing test case soon.

### Steps to reproduce

1. Create a component whose template renders `turbo-stream` tags using the helper provided by `turbo-rails`.
2. Render this in a controller action as follows:

```rb
respond_to { |format| format.turbo_stream { render ExampleComponent.new } }
```

### Expected behavior

The content given to each use of the `turbo_stream.update` helper should be rendered inside `turbo-stream` tags.

### Actual behavior

Some content is captured and rendered before empty `turbo-stream` tags.

### System configuration
**Rails version**: 7.0.0.alpha2

**Ruby version**: 2.7.4

**Gem version**: 2.40.0

&block rendered twice or at the wrong position from helper
I have a custom form builder but since I want to be able to use the default one as well I created helper method for it:
```
# form_helper.rb

module FormHelper
  def my_form_with(**args, &block)
    form_with(builder: MyFormBuilder, **args, &block)
  end
end
```

And when I try to use it inside any component I get two different results but neither of them is what I expect it to be. One renders the block twice - before and inside the `form` tag. The other one renders the block only before the `form` tag.

Note that when I use the `form_with` directly in the component's template it works so this has something to do with it being called from the helper.

### Steps to reproduce
Create helper that receives `&block`, pass that block to any rails helper like `form_with`, `link_to`, etc. Use the created helper in the component.

### Expected behavior
It should render the block inside the `form` tag and only once. The same way it works when used outside the component

### Actual behavior
Example 1:
```
# component.html.erb

<%= helpers.my_form_with do %>
  Example string
<% end>
```

Result:
```
Example string

<form action="..." method="post">
    Example string
</form>
```

Example 2:
```
# component.html.erb

<%= helpers.my_form_with(model: Model) do |form| %>
  <% form.text_field(:title) %>
<% end>
```

Result:
```
<input id="model_title" type="text" name="model[title]">

<form action="..." method="post">
</form>
```

### System configuration
**Rails version**: 6.1

**Ruby version**: 2.7.6

**Gem version**: 2.56.2

helpers.link_to with a block causes broken HTML output
`helpers.link_to` does not work with a block. Removing `helpers` prefix fixes the behavior. 

However, it is very common to use `link_to` in view helpers. A simple usage is marking the links as active or inactive based on the current URL:

```ruby
module LinksHelper
  def link_to_active(name = nil, options = {}, html_options = {}, &block)
    # For simplicity logic omitted

    link_to(name, options, html_options, &block)
  end
end
```

Usage of `link_to_active` with a block just breaks `view_component`.

### Steps to reproduce

Inside a component, render:

```
<%= helpers.link_to "/", class: "flex" do %>
  <%= helpers.heroicon :check %>
  <span class="ml-2">Link</span>
<% end %>
```

### Expected behavior

```HTML
<a class="flex" href="/">
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5">
    <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
  </svg>
  <span class="ml-2">Link</span>
</a>
```

### Actual behavior

It renders incorrect HTML:

```HTML
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5">
  <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
<span class="ml-2">Link</span>
<a class="flex" href="/">
  &lt;span class="ml-2"&gt;Link&lt;/span&gt;
</a>
```

### System configuration

**Rails version**: Rails 7.0.3.1
**Ruby version**: ruby 3.1.2p20
**Gem version**: 2.66.0

Double Output When Rendering HAML Partials Containing Form Helpers
When using view component (ERB, HAML, whatever) to render a form,
and then rendering a partial from that component,
if that component is both written in HAML and contains a tag like `fields_for`,
the output will be doubled.

This does not happen if the partial doesn't contain such a tag, or if the partial is written in ERB, or if the partial is rendered from a normal view without VC. So, all three conditions are required.

### Steps to reproduce
Here is a link to a repository demonstrating the behavior: https://github.com/Aesthetikx/view_component_haml_partial_double

### Expected behavior
![plain_form](https://user-images.githubusercontent.com/1302406/207443681-51c58f1c-38c5-4f62-a0d9-efff780e2375.png)

### Actual behavior
![haml_partial](https://user-images.githubusercontent.com/1302406/207443714-c558b1e7-59e6-46bd-9713-ee828a6c44b1.png)

### System configuration
Rails 7.0.4
Ruby 3.1.2
ViewComponent 2.79.0
HAML 6.1.1
duplicate elements when combining helpers and <%=
### Steps to reproduce
this component:
```erb
<div>
  <%= helpers.tag.div do %>
    content of helper and =
  <% end %>

  <% helpers.tag.div do %>
    content of helper and no =
  <% end %>

  <% tag.div do %>
    content of no helper and no =
  <% end %>

  <%= tag.div do %>
    content of no helper and =
  <% end %>
</div>
```

### Expected behavior
```html
<div>
  <div>
    content of helper and =
  </div>

  <div>
    content of no helper and =
  </div>
</div>
```

### Actual behavior
```html
<div>
  content of helper and =
  <div>
    content of helper and =
  </div>
  content of helper and no =
  <div>
    content of no helper and =
  </div>
</div>
```
its as if when we are using the `helpers` keyword, it gets rendered even if we use `<%` instead of `<%=`. And when we do user `<%=`, then it renders it in double.

i'm 99% sure its the root cause of issue https://github.com/ViewComponent/view_component/issues/1099 and https://github.com/viewcomponent/view_component/issues/241, and might be related to some other issues like https://github.com/viewcomponent/view_component/issues/241

### System configuration
**Rails version**:
Rails 7.0.3.1
**Ruby version**:
ruby 3.1.2p20
**Gem version**:
2.62.0


Discussion: This looks really similar to the issue I ran into that caused me to open https://github.com/github/actionview-component/pull/240.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Just ran into this and for a simple case temporarily solved it by doing 
```
<%# 
  <% label_contents = '
    <span class="form-file-text">Cambiar imagen</span>
    <span class="form-file-button">Abrir</span>
  ' %>

  <%= @form.label(@attached_method, raw(label_contents), class: "form-file-label") %>
```
It seems that this error is happening with `link_to` code blocks and `tag.<tagname>` blocks too. 

Any reason why this is happening?

Hey @pacMakaveli! It'd be really helpful if you could provide us with a failing test case or a replica of the issue. If you want to get started with the latter, I'd recommend doing the following:

```
rails new --minimal view_component-1099
cd view_component-1099
bundle add view_component
rails g component TurboFrameTag
```

A failing test case is preferable, but either way, a replica of the issue in isolation will be helpful in diagnosing it. Thanks!
@pacMakaveli I've run into the same issues as well if I remember right it come from the [known `form_for` issue](https://github.com/github/view_component/issues/241) linked in the docs.

Any way a work around we are using is to write the `turbo-frame id="myid"` tag instead of using the `turbo_frame_tag` helper.

Chris
Part of me wonders if #1090 is also linked to this. All of these see us using helpers that are available to Action View *without* us having to chain off `helpers`.

Would calling `helpers.turbo_frame_tag` instead of `turbo_frame_tag` fix this in the short term? 
> Would calling `helpers.turbo_frame_tag` instead of `turbo_frame_tag` fix this in the short term?

Afraid not, however, @xkraty's solution works a treat.
Either:
```html
<turbo-frame id="some_id">
  Some content
</turbo-frame>
```
Or:
```ruby
<%= tag.turbo_frame(id: "some_id") do %>
  Some content
<% end %>
```
Adding `include Turbo::FramesHelper` in your component class seems to work as well: you can then call `turbo_frame_tag` directly instead of proxying through `helpers`
@swanson it didn't work for me. Nor did the `helpers.turbo_frame_tag`
However, @HashNotAdam's solution works a treat! Thanks a bunch to everyone that chimed in.

So, if you have issues with the ViewComponent duplicating your content, or messing with the HTML rendering, `tag.turbo_frame` fixes it.


@pacMakaveli mind writing a docs PR to reflect this?
The same happened for me after upgrading `haml` from 5 to 6.  
Using `tag.turbo_frame` fixed it for me.

I think this could be resolved by using a single output buffer if we come to do it again. In the meantime, [this](https://github.com/ViewComponent/view_component/issues/1322#issuecomment-1172315202) might work since it's akin to rendering a component in a controller context.

See #974 for the WIP on this issue 🙂
I have found some issues with the similar problem but totally missed the PR, thank you!
I believe that another PR aimed to resolve this issue was actually merged (https://github.com/github/view_component/pull/1307), and then subsequently reverted: https://github.com/github/view_component/pull/1432.

I've recently run into this issue, and I was wondering if there are any efforts being made to address it, now that we are no longer experimenting with a single output buffer.

FYI this appears to be a variation of the behavior described in #1384, which is rooted in #974 
Yes, it seems to be rooted in https://github.com/ViewComponent/view_component/pull/974. However, it seems the work has been abandoned in favor of https://github.com/ViewComponent/view_component/pull/1307, which was reverted by https://github.com/ViewComponent/view_component/pull/1432.

Just stumbled into another issue while trying to use `turbo_frame_tag` with a block as already described in  https://github.com/ViewComponent/view_component/issues/1099. Seems to be the same root issue.

For easy access, [this partial](https://github.com/Aesthetikx/view_component_haml_partial_double/blob/main/app/views/books/_author_fields.haml) is rendered [here](https://github.com/Aesthetikx/view_component_haml_partial_double/blob/7c44ba988a4785c8e9d3813faf8cefd11ec30e54/app/components/book_form_component.html.erb#L21), and the component is rendered [here](https://github.com/Aesthetikx/view_component_haml_partial_double/blob/7c44ba988a4785c8e9d3813faf8cefd11ec30e54/app/views/books/new.html.erb#L5).
Possibly related to https://github.com/viewcomponent/view_component/issues/241.





Fix (patch):
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 188c1ddc4..494631f10 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -29,14 +29,34 @@ jobs:
         include:
           - rails_version: "5.2.6"
             ruby_version: "2.7"
+            mode: "capture_patch_enabled"
           - rails_version: "6.0.4.4"
             ruby_version: "2.7"
+            mode: "capture_patch_enabled"
           - rails_version: "6.1.4.4"
             ruby_version: "2.7"
+            mode: "capture_patch_enabled"
           - rails_version: "7.0.2.3"
             ruby_version: "3.0"
+            mode: "capture_patch_enabled"
           - rails_version: "main"
             ruby_version: "3.1"
+            mode: "capture_patch_enabled"
+          - rails_version: "5.2.6"
+            ruby_version: "2.7"
+            mode: "capture_patch_disabled"
+          - rails_version: "6.0.4.4"
+            ruby_version: "2.7"
+            mode: "capture_patch_disabled"
+          - rails_version: "6.1.4.4"
+            ruby_version: "2.7"
+            mode: "capture_patch_disabled"
+          - rails_version: "7.0.2.3"
+            ruby_version: "3.0"
+            mode: "capture_patch_disabled"
+          - rails_version: "main"
+            ruby_version: "3.1"
+            mode: "capture_patch_disabled"
     steps:
     - uses: actions/checkout@master
     - name: Setup Ruby
@@ -56,6 +76,7 @@ jobs:
         RAISE_ON_WARNING: 1
         MEASURE_COVERAGE: true
         RAILS_VERSION: ${{ matrix.rails_version }}
+        CAPTURE_PATCH_ENABLED: ${{ matrix.mode == 'capture_patch_enabled' && 'true' || 'false' }}
     - name: Upload coverage results
       uses: actions/upload-artifact@master
       if: always()
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index c94e6b000..91712f012 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -10,6 +10,10 @@ nav_order: 5
 
 ## main
 
+* Add experimental `config.view_component.capture_compatibility_patch_enabled` option resolving rendering issues related to forms, capture, turbo frames, etc.
+
+    *Blake Williams*
+
 * Add `#content?` method that indicates if content has been passed to component.
 
     *Joel Hawksley*
diff --git a/lib/view_component.rb b/lib/view_component.rb
index 3570ad13e..8172ffcf7 100644
--- a/lib/view_component.rb
+++ b/lib/view_component.rb
@@ -7,6 +7,7 @@ module ViewComponent
   extend ActiveSupport::Autoload
 
   autoload :Base
+  autoload :CaptureCompatibility
   autoload :Compiler
   autoload :CompileCache
   autoload :ComponentError
diff --git a/lib/view_component/capture_compatibility.rb b/lib/view_component/capture_compatibility.rb
new file mode 100644
index 000000000..553c3e5d8
--- /dev/null
+++ b/lib/view_component/capture_compatibility.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module ViewComponent
+  # CaptureCompatibility is a module that patches #capture to fix issues
+  # related to ViewComponent and functionality that relies on `capture`
+  # like forms, capture itself, turbo frames, etc.
+  #
+  # This underlying incompatibility with ViewComponent and capture is
+  # that several features like forms keep a reference to the primary
+  # `ActionView::Base` instance which has its own @output_buffer. When
+  # `#capture` is called on the original `ActionView::Base` instance while
+  # evaluating a block from a ViewComponent the @output_buffer is overridden
+  # in the ActionView::Base instance, and *not* the component. This results
+  # in a double render due to `#capture` implementation details.
+  #
+  # To resolve the issue, we override `#capture` so that we can delegate
+  # the `capture` logic to the ViewComponent that created the block.
+  module CaptureCompatibility
+    def self.included(base)
+      base.class_eval do
+        alias_method :original_capture, :capture
+      end
+
+      base.prepend(InstanceMethods)
+    end
+
+    module InstanceMethods
+      def capture(*args, &block)
+        # Handle blocks that originate from C code and raise, such as `&:method`
+        return original_capture(*args, &block) if block.source_location.nil?
+
+        block_context = block.binding.receiver
+
+        if block_context != self && block_context.class < ActionView::Base
+          block_context.original_capture(*args, &block)
+        else
+          original_capture(*args, &block)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/view_component/config.rb b/lib/view_component/config.rb
index 981be681a..4ee66a436 100644
--- a/lib/view_component/config.rb
+++ b/lib/view_component/config.rb
@@ -23,7 +23,8 @@ def defaults
           show_previews: Rails.env.development? || Rails.env.test?,
           preview_paths: default_preview_paths,
           test_controller: "ApplicationController",
-          default_preview_layout: nil
+          default_preview_layout: nil,
+          capture_compatibility_patch_enabled: false
         })
       end
 
@@ -137,6 +138,13 @@ def defaults
       # A custom default layout used for the previews index page and individual
       # previews.
       # Defaults to `nil`. If this is falsy, `"component_preview"` is used.
+      #
+      # @!attribute capture_compatibility_patch_enabled
+      # @return [Boolean]
+      # Enables the experimental capture compatibility patch that makes ViewComponent
+      # compatible with forms, capture, and other built-ins.
+      # previews.
+      # Defaults to `false`.
 
       def default_preview_paths
         return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews")
diff --git a/lib/view_component/engine.rb b/lib/view_component/engine.rb
index 45f65900a..9f8c1952a 100644
--- a/lib/view_component/engine.rb
+++ b/lib/view_component/engine.rb
@@ -47,6 +47,14 @@ class Engine < Rails::Engine # :nodoc:
       end
     end
 
+    # :nocov:
+    initializer "view_component.enable_capture_patch" do |app|
+      ActiveSupport.on_load(:view_component) do
+        ActionView::Base.include(ViewComponent::CaptureCompatibility) if app.config.view_component.capture_compatibility_patch_enabled
+      end
+    end
+    # :nocov:
+
     initializer "view_component.set_autoload_paths" do |app|
       options = app.config.view_component