Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ nav_order: 6

## main

* Fix double rendering issue for partials that yield.

*Cameron Dutro*

## 4.0.1

* Setup Trusted Publishing to RubyGems to improve software supply chain safety.
Expand Down
23 changes: 19 additions & 4 deletions lib/view_component/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -246,17 +246,32 @@ def render?

# Re-use original view_context if we're not rendering a component.
#
# This prevents an exception when rendering a partial inside of a component that has also been rendered outside
# of the component. This is due to the partials compiled template method existing in the parent `view_context`,
# and not the component's `view_context`.
# As of v4, ViewComponent::Base re-uses the existing view context created
# by ActionView, meaning the current view context and the original view
# context are the same object. set_original_view_context is still called
# to maintain backwards compatibility.
#
# @private
def render(options = {}, args = {}, &block)
if options.respond_to?(:set_original_view_context)
options.set_original_view_context(self.__vc_original_view_context)

# We assume options is a component, so there's no need to evaluate the
# block in the view context as we do below.
@view_context.render(options, args, &block)
elsif block
__vc_original_view_context.render(options, args) do
# Partials are rendered to their own buffer and do not append to the
# original @output_buffer we retain a reference to in #render_in. This
# is a problem since the block passed to us here in the #render method
# is evaluated within the context of ViewComponent::Base, and thus
# appends to the original @output_buffer. To avoid this, we evaluate the
# block in the view context instead, which will append to the output buffer
# created for the partial.
__vc_original_view_context.instance_exec(&block)
end
else
__vc_original_view_context.render(options, args, &block)
__vc_original_view_context.render(options, args)
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<%= render "shared/yielding_partial" do %>
world
<% end %>
2 changes: 2 additions & 0 deletions test/sandbox/app/components/partial_with_yield_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class PartialWithYieldComponent < ViewComponent::Base
end
1 change: 1 addition & 0 deletions test/sandbox/app/views/shared/_yielding_partial.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello <%= yield %>
5 changes: 5 additions & 0 deletions test/sandbox/test/rendering_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1317,4 +1317,9 @@ def test_around_render

assert_text("Hi!")
end

def test_render_partial_with_yield
render_inline(PartialWithYieldComponent.new)
assert_text "hello world", exact: true, normalize_ws: true
end
end
Loading