Skip to content

BREAKING: Allow with_content to override content passed as a block. #2346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from

Conversation

joelhawksley
Copy link
Member

@joelhawksley joelhawksley commented Jun 11, 2025

For example, slots can now have their content set with with_content:

render(ParentSlotComponent.new) do |parent|
  parent.with_child do |child|
    child.with_title { "Child Title" }
    child.with_content("Child Content")
  end
end

In practice, this addresses the case where with_content was disallowed due to the block passed to with_child, despite the block not providing content.

Closes #2345

view_context.capture(self, &@__vc_render_in_block)
elsif __vc_content_set_by_with_content_defined?
if __vc_content_set_by_with_content_defined?
@__vc_content_set_to_with_content_value = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My brain is having a really hard time parsing what this variable's name means. Is there a better name we can use? Idea: @__vc_content_set_via_static_value

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also having a hard time reading this code, I think because of how similar the variable names are here. Maybe there's shorter names we could apply?

Comment on lines +21 to +35
* BREAKING: Allow `with_content` to override content passed as a block. `DuplicateContentError` and `DuplicateSlotContentError` will no longer be raised.

For example, components and slots can now have their content set with `with_content` even when used with block syntax:

```ruby
render(ParentSlotComponent.new.with_content("Parent Content")) do |parent|
parent.with_child do |child|
child.with_title { "Child Title" }
child.with_content("Child Content")
end
end
```

*Joel Hawksley*

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love to see what the rendered result of this is in the docs, i.e. show that slots are defined and the content provided with with_content also renders.

Comment on lines -57 to -67
class DuplicateContentError < StandardError
MESSAGE =
"It looks like a block was provided after calling `with_content` on COMPONENT, " \
"which means that ViewComponent doesn't know which content to use.\n\n" \
"To fix this issue, use either `with_content` or a block."

def initialize(klass_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
end
end

Copy link
Contributor

@camertron camertron Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to get rid of this error? If you call with_content more than once, is it ok that the previous value gets overwritten with no warning? Fine either way, but is that behavior documented anywhere?

Copy link
Contributor

@BlakeWilliams BlakeWilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the approach here, but I can't help but feel this is a workaround our existing API that the "content as a slot" solution would fix. Ultimately, it seems problematic to continue conflating executing API code in the block with the block returning content.

If we separated the two, we could keep our error messages around duplicative slot content and have a (potentially) simpler implementation.

view_context.capture(self, &@__vc_render_in_block)
elsif __vc_content_set_by_with_content_defined?
if __vc_content_set_by_with_content_defined?
@__vc_content_set_to_with_content_value = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also having a hard time reading this code, I think because of how similar the variable names are here. Maybe there's shorter names we could apply?

@@ -5,6 +5,7 @@ module WithContentHelper
def with_content(value)
raise NilWithContentError if value.nil?

@__vc_content_set_to_with_content_value = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need to be called?

@joelhawksley
Copy link
Member Author

I understand the approach here, but I can't help but feel this is a workaround our existing API that the "content as a slot" solution would fix. Ultimately, it seems problematic to continue conflating executing API code in the block with the block returning content.

Having sat on this PR for the weekend, I agree. I was kind of hoping this PR might help us avoid the content-as-a-slot work, but I think it's too much of a half-measure. Closing.

@joelhawksley joelhawksley deleted the 2264-slots-with-content branch June 16, 2025 19:50
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.

3 participants