joshleblanc / view_component_reflex

Call component methods right from your markup
http://view-component-reflex-expo.grep.sh/
MIT License
291 stars 27 forks source link

Losing slots after reflex #95

Open omarluq opened 1 year ago

omarluq commented 1 year ago

In my app I have the following dropdown menu component when the open function is triggered the menu component is losing all of its slots, am I missing something? I've never encountered this issue before

module Ui
  module Dropdown
    class MenuComponent < ApplicationComponent
      renders_one :trigger, -> { Ui::Dropdown::TriggerComponent.new(data: reflex_data_attributes(:open).merge({ action: 'keydown->application-controller#stopProp' })) }
      renders_many :sections, ->(**system_arguments) { Ui::Dropdown::SectionComponent.new(**system_arguments) }

      def initialize(classes: '')
        @classes = classes
        @role = 'menu'
        @hidden = 'hidden'
        @transition = 'transition ease-out duration-300'
        @transform = 'translate-y-2 opacity-0 scale-95'
      end

      def collection_key
        SecureRandom.hex(16)
      end

      def open
        @hidden = ''
        @transition = 'transition ease-in duration-600'
        @transform = 'translate-y-0 opacity-100 scale-100'
      end

      def close
        @hidden = 'hidden'
        @transition = 'transition ease-out duration-300'
        @transform = 'translate-y-2 opacity-0 scale-95'
      end

      def menu_key
        "menu_for_#{key}"
      end
    end
  end
end
<%= component_controller do %>
  <div class="relative inline-block text-left">
    <%= trigger %>
    <div id="<%= menu_key %>" class="<%= "#{@hidden} #{@transition} #{@transform} absolute right-0 mt-2 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none w-max" %>" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1" style="z-index:999999">
        <% sections.each do |section| %>
          <%= section %>
        <% end %>
    </div>
    <div id='hidden-close-button-for-active-dropdown' class="<%= "#{@hidden} fixed inset-0 w-full h-full" %>" data-reflex="click->Ui::Dropdown::MenuComponent#close" data-key="<%= key %>" style="z-index:9997"></div>
  </div>
<% end %>
omarluq commented 1 year ago

P.S I can fix the issue by adding data-reflex-permanent tags on a div a wrap the trigger and the sections with it but is that an optimal solution?

omarluq commented 1 year ago

@julianrubisch @joshleblanc any ideas?

joshleblanc commented 1 year ago

I talked with Omar awhile ago, and I think he found a workaround. Leaving this open because I'm pretty sure it's still a bug.

omarluq commented 1 year ago

After you agreed to whitelist the cable_ready helper on the components I ended up switching the page morphs to add_css_class and remove_css_class. Another fix that I found at the time was using printing tags when calling the with_slot_name helper ex:

<%= render(Component.new) do |c|%> 
  <%= c.with_slot %>
<% end %>

But the ViewComponent docs recommend using non-printing tags for slots!

Laykou commented 1 year ago

I see slots are currently still not supported, as ViewComponentReflex ignores any @__vc instance variable.

I assume that's because it's problematic to serialize/deserialize them:

https://github.com/joshleblanc/view_component_reflex/blob/master/lib/view_component_reflex/component.rb#L243

 def safe_instance_variables
  instance_variables.reject { |ivar| ivar.start_with?("@__vc") } - unsafe_instance_variables - omitted_from_state
end
Laykou commented 1 year ago

My quick workaround:

Example:

# my_component.rb
class MyComponent < ApplicationViewComponent
  renders_one :hello_world

  def initialize(partial_path:, other_state: {})
    @partial_path = partial_path
    @other_state = other_state
  end

  def before_render
      super

      render partial: @partial_path, locals: { component: self } if @partial_path
  end
end
- # my_component.html.haml
= hello_world
- # show.html.haml
= render MyComponent.new(partial_path: '_partial_component_content', other_state: {})
- # _partial_component_content.html.haml
- component.with_hello_world do
  Hello world!
omarluq commented 1 year ago

@Laykou I think that this workaround is very similar to using printing tags to call your slots, both solutions force a recall of the slots block on every refresh pretty much