Skip to content

Commit a755f3c

Browse files
authored
Merge pull request #2424 from ViewComponent/fix_partial_with_yield
Fix double rendering issue for partials that yield
2 parents 1ed16e3 + e2cefa9 commit a755f3c

File tree

6 files changed

+34
-4
lines changed

6 files changed

+34
-4
lines changed

docs/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ nav_order: 6
1010

1111
## main
1212

13+
* Fix double rendering issue for partials that yield.
14+
15+
*Cameron Dutro*
16+
1317
## 4.0.1
1418

1519
* Setup Trusted Publishing to RubyGems to improve software supply chain safety.

lib/view_component/base.rb

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,17 +246,32 @@ def render?
246246

247247
# Re-use original view_context if we're not rendering a component.
248248
#
249-
# This prevents an exception when rendering a partial inside of a component that has also been rendered outside
250-
# of the component. This is due to the partials compiled template method existing in the parent `view_context`,
251-
# and not the component's `view_context`.
249+
# As of v4, ViewComponent::Base re-uses the existing view context created
250+
# by ActionView, meaning the current view context and the original view
251+
# context are the same object. set_original_view_context is still called
252+
# to maintain backwards compatibility.
252253
#
253254
# @private
254255
def render(options = {}, args = {}, &block)
255256
if options.respond_to?(:set_original_view_context)
256257
options.set_original_view_context(self.__vc_original_view_context)
258+
259+
# We assume options is a component, so there's no need to evaluate the
260+
# block in the view context as we do below.
257261
@view_context.render(options, args, &block)
262+
elsif block
263+
__vc_original_view_context.render(options, args) do
264+
# Partials are rendered to their own buffer and do not append to the
265+
# original @output_buffer we retain a reference to in #render_in. This
266+
# is a problem since the block passed to us here in the #render method
267+
# is evaluated within the context of ViewComponent::Base, and thus
268+
# appends to the original @output_buffer. To avoid this, we evaluate the
269+
# block in the view context instead, which will append to the output buffer
270+
# created for the partial.
271+
__vc_original_view_context.instance_exec(&block)
272+
end
258273
else
259-
__vc_original_view_context.render(options, args, &block)
274+
__vc_original_view_context.render(options, args)
260275
end
261276
end
262277

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<%= render "shared/yielding_partial" do %>
2+
world
3+
<% end %>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class PartialWithYieldComponent < ViewComponent::Base
2+
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello <%= yield %>

test/sandbox/test/rendering_test.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,4 +1317,9 @@ def test_around_render
13171317

13181318
assert_text("Hi!")
13191319
end
1320+
1321+
def test_render_partial_with_yield
1322+
render_inline(PartialWithYieldComponent.new)
1323+
assert_text "hello world", exact: true, normalize_ws: true
1324+
end
13201325
end

0 commit comments

Comments
 (0)