Skip to content

Fix yielded content rendered at wrong location with Rails form helpers#2619

Draft
joelhawksley wants to merge 1 commit intomainfrom
joelhawksley/fix-form-helper-yield-location
Draft

Fix yielded content rendered at wrong location with Rails form helpers#2619
joelhawksley wants to merge 1 commit intomainfrom
joelhawksley/fix-form-helper-yield-location

Conversation

@joelhawksley
Copy link
Copy Markdown
Member

Problem

When a ViewComponent renders a partial that uses a Rails form helper (like form.label) with a yielding block, the yielded content is rendered outside the helper's tag instead of inside it.

Expected: <label>world</label>
Actual: world <label></label>

Reported in #2617 — this PR provides the fix.

Root Cause

form_with creates form builders with @template_object pointing to the component. When the form builder is later used inside a partial (e.g. form.label :something do ... end), it calls @template_object.capture(builder, &block) — capturing on the component's @output_buffer.

However, Rails' Template#render_run has already replaced view_context.@output_buffer with a fresh OutputBuffer for the partial. The compiled partial template code writes to this new buffer, while the component's @output_buffer still references the original one set during render_in.

This mismatch means form.label's capture reads from an empty (stale) buffer, and the block's output leaks into the partial's main output stream instead of being wrapped by the label tag.

Fix

Override capture in ViewComponent::Base to synchronize @output_buffer with view_context.output_buffer before delegating to super. This ensures form helpers that capture through the component use the same buffer that the block's template code writes to.

def capture(*, **, &block)
  old_output_buffer = @output_buffer
  @output_buffer = view_context.output_buffer if view_context
  super
ensure
  @output_buffer = old_output_buffer
end

Test

Adds a test case with a component that renders a partial using form.label with yield, verifying the yielded content appears inside the label tag.

When a component renders a partial that uses a Rails form helper (like
form.label) with a yielding block, the yielded content was rendered
outside the helper's tag instead of inside it.

Root cause: form_with creates form builders with @template_object
pointing to the component. When form.label calls capture through the
component during partial rendering, the component's @output_buffer
(set once in render_in) is stale — Rails' _run has already replaced
the view context's output buffer with a fresh one for the partial.
The capture reads from the wrong buffer while the block writes to the
partial's buffer, causing the content to leak.

Fix: Override capture in ViewComponent::Base to sync @output_buffer
with view_context.output_buffer before delegating to super.

Fixes #2617

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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

Successfully merging this pull request may close these issues.

1 participant