Skip to content
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

Component previews that aren't declared with templates don't appear to support slots #1322

Closed
boardfish opened this issue Mar 29, 2022 Discussed in #1321 · 21 comments
Closed
Labels
slots Related to the Slots feature

Comments

@boardfish
Copy link
Collaborator

Discussed in #1321

I was able to replicate this issue raised by @muriloime. With these files in place (preview in test/components/previews/, the rest in app/components), navigate to http://localhost:3000/rails/view_components/outer_component/default.

You'd expect the preview to render as follows:

<div>
  <p>OuterComponent</p>
  <div>Add Inner template here</div>

</div>

But instead, it renders like this:

<div>
  <p>OuterComponent</p>
  
</div>

Presumably this is to say the content of the slot is ignored.

The interim fix for this is to use preview templates to render your component.

This does feel familiar to me, and could be a duplicate or something we've previously overlooked or not had time to fix.

Originally posted by muriloime March 29, 2022
Hi all, The components do not show when inside slots in a preview. Example:

outer_component.rb

class OuterComponent
 renders_one :button 
end 

outer_component preview

    render(OuterComponent.new) do |c|
      c.button { 'My awesome button' }
    end

This works as expected and documented in https://viewcomponent.org/guide/slots.html. However, when using a component:

    render(OuterComponent.new) do |c|
      c.button { render InnerComponent.new }
    end

does not work ( inner component does not render. I tried both with and without the inner render ) . Am I doing something stupid here or there is a bug?

many thanks

@muriloime
Copy link

Thanks @boardfish . I assume the same behaviour happens when testing compnonents, but I haven't tried it yet

@joelhawksley
Copy link
Member

@boardfish thanks for moving this to an issue ❤️

To my knowledge, this issue also exists in the controller context. Perhaps we could start with a failing test for this use case for previews and controllers? @muriloime would you be up for starting there?

@muriloime
Copy link

HI @joelhawksley . Indeed, the problem exists in the controller context too.

I am still getting my head around the view_component code, but the branch https://github.com/muriloime/view_component/tree/fix_slot_rendering contains the two failing tests .

cheers
Murilo

@muriloime
Copy link

Any hints on how to fix this @boardfish, @joelhawksley? thanks

@joelhawksley
Copy link
Member

@muriloime thanks for providing a failing test! I've been traveling and haven't had a chance to dig into this further.

FWIW, rendering content in blocks in a controller context is something Rails does not support to my knowledge. That likely means that getting something working here could be tricky.

That being said, if this is time sensitive for you I'd be happy to do some pairing time to get things started if you'd be interested in figuring out a path forward from there: joelhawksley@github.com ❤️

@muriloime
Copy link

That would be great, @joelhawksley, many thanks. I will reach out by email then.

By digging a little bit I see the data vanishes at this line:

https://github.com/github/view_component/blob/e7eec9869fc13e401401c687573a98f1b0ed57a8/lib/view_component/slot_v2.rb#L58

cheers
Murilo

@jacob-carlborg-apoex
Copy link
Contributor

I've run into this issue as well, rendering a component with a block in a controller.

FWIW, rendering content in blocks in a controller context is something Rails does not support to my knowledge

@joelhawksley the API does support a block [1], as far as I can see. But it still doesn't work. I've tried to trace down where it goes wrong, but there are so many layers in Rails, it's easy to get lost 😃.

The ViewComponent documentation has an example of this use case [2], perhaps it should be removed until it works.

[1] https://api.rubyonrails.org/v6.1.4/classes/AbstractController/Rendering.html#method-i-render
[2] https://viewcomponent.org/guide/getting-started.html#rendering-from-controllers

@DRBragg
Copy link
Contributor

DRBragg commented Jun 2, 2022

Thanks @boardfish . I assume the same behaviour happens when testing compnonents, but I haven't tried it yet

Can confirm this is also happening when testing. With Rspec at least

@joelhawksley
Copy link
Member

@jacob-carlborg-apoex #1404 ❤️

@stevegeek
Copy link

Just saw this and throwing in a possible work around instead of using render_with_template

Im pretty sure you can call render on the outer component to ensure the inner component renders in its context, ie

render(OuterComponent.new) do |c|
  c.button { c.render InnerComponent.new }
end

At least thats what Im doing in my previews, eg

render ::NotificationComponent.new(title: title) do |notification|
  notification.avatar { notification.render ::AvatarComponent.new(...) }
end

@PedroAugustoRamalhoDuarte

I have the same problem with with_content method

  def with_component_inside
    render(
      Reports::Accordion::Component.new(name: "Habilidades").with_content(
        render(Reports::Sections::SituationSection::Component.new(below_average_quantity: 10, basic_quantity: 567,
                                                                  adequate_quantity: 90, advanced_quantity: 57))
      )
    )
  end

@boardfish
Copy link
Collaborator Author

@PedroAugustoRamalhoDuarte #render appends to the output buffer that your controller returns or your view renders. When you're passing something to #with_content, it needs to be a string so that it can be interpolated by the template correctly. You may want to use #render_in instead here.

@boardfish
Copy link
Collaborator Author

Come to think of it, that might even be an interim solution here if you really need slot functionality when rendering in this style, but I also feel that how you render components in the controller and view template shouldn't really differ, so I wouldn't say it's optimal.

joelhawksley added a commit that referenced this issue Jul 5, 2022
* update docs around rendering in controllers
see #1322

* vale

* grammar

* Update docs/guide/getting-started.md

Co-authored-by: Simon Fish <si@mon.fish>

Co-authored-by: Simon Fish <si@mon.fish>
@HermantNET
Copy link

@PedroAugustoRamalhoDuarte #render appends to the output buffer that your controller returns or your view renders. When you're passing something to #with_content, it needs to be a string so that it can be interpolated by the template correctly. You may want to use #render_in instead here.

Thank you, you saved me a lot of effort haha. If anyone else stumbles upon this thread, rendering components in slots in previews is pretty easy:

class TableComponentPreview < ViewComponent::Preview
  def default
    render(TableComponent.new) do |c|
      c.with_head do
        TableHeadComponent.new(
          columns: ['First Name', 'Middle Name', 'Last Name'],
        )
        .render_in(c)
      end
    end
  end
end

@robyurkowski
Copy link

Not to necro-post, but I want to add that the above solution worked equally well with the default content slot:

render(CardComponent.new) do |c|
  PlaceholderComponent.new.render_in(c)
end
SEO juice

Here's the search terms I tried to use, which will hopefully help some other poor sap find this:

  • Rails ViewComponent Preview Nested Render

claudiob pushed a commit to claudiob/view_component that referenced this issue Dec 22, 2023
* update docs around rendering in controllers
see ViewComponent#1322

* vale

* grammar

* Update docs/guide/getting-started.md

Co-authored-by: Simon Fish <si@mon.fish>

Co-authored-by: Simon Fish <si@mon.fish>
claudiob pushed a commit to claudiob/view_component that referenced this issue Jan 3, 2024
* update docs around rendering in controllers
see ViewComponent#1322

* vale

* grammar

* Update docs/guide/getting-started.md

Co-authored-by: Simon Fish <si@mon.fish>

Co-authored-by: Simon Fish <si@mon.fish>
@unikitty37
Copy link

This works for a single component, but multiple components result in only the last one being shown:

render(CardComponent.new) do |c|
  PlaceholderComponent.new(title: 'One').render_in(c)
  PlaceholderComponent.new(title: 'Two').render_in(c)
end

shows only the placeholder with the title "Two" in the slot. The docs don't really address this use case.

@reeganviljoen
Copy link
Collaborator

@joelhawksley do you think we should re-open this issue or make a new issue with a link to this in-light of these new revelations

@Spone
Copy link
Collaborator

Spone commented Mar 18, 2024

@unikitty37 this behavior makes sense to me, since only the latest line is returned by the block.

You can try Rails' concat method:

render(CardComponent.new) do |c|
  concat PlaceholderComponent.new(title: 'One').render_in(c)
  concat PlaceholderComponent.new(title: 'Two').render_in(c)
end

or safe_join:

render(CardComponent.new) do |c|
  safe_join([
    PlaceholderComponent.new(title: 'One').render_in(c),
    PlaceholderComponent.new(title: 'Two').render_in(c)
  ])
end

@unikitty37
Copy link

@Spone Thanks — concat didn't work for me in the preview, but safe_join did.

@reeganviljoen
Copy link
Collaborator

@unikitty37 @Spone do you think a note in the docs or something equivalent would be appropriate

@Spone
Copy link
Collaborator

Spone commented Mar 19, 2024

I'm not sure we ought to document Rails' helpers, but if you feel it's unclear they can be useful in this case, we can add a note about this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
slots Related to the Slots feature
Projects
None yet
Development

No branches or pull requests