ViewComponent / view_component

A framework for building reusable, testable & encapsulated view components in Ruby on Rails.
https://viewcomponent.org
MIT License
3.2k stars 410 forks source link

Add forms section to guide #908

Open joelhawksley opened 3 years ago

joelhawksley commented 3 years ago

Problem statement

While we do share that ViewComponents don't work well with form_for, this information is hard to find under known issues. There is no suggested alternative or approach.

Proposed solution

Add a Forms section to the guide:

pinzonjulian commented 3 years ago

Hey! I want to take a stab at this if it's ok.

For the past year my team and I have worked on a Rails app that uses ViewComponent in replacement of ActionView and we've had no problems with forms. I've seen the issue for the form_for compatibility problem and I believe it's either a) an edge case that shouldn't get people discouraged from using view component or b) a misuse of view components in the context of Rails forms.

Do you have something in particular you'd like to see in the docs for forms? Or can I just send a first version of what I believe the docs for forms should be and we take it from there?

joelhawksley commented 3 years ago

can I just send a first version of what I believe the docs for forms should be and we take it from there?

Please do!

jcoyne commented 3 years ago

I want to add that I also have an app that uses form_for with view_components and we haven't had any trouble other than the fact that capture helpers seem to not work in the view-component context. We just work around by switching <%= to <%.

pinzonjulian commented 3 years ago

@joelhawksley the PR that closes this issue is up! https://github.com/github/view_component/pull/946

@jcoyne I'd love for you to take a look at it if you have the time. I haven't run into the capture issue you mentioned but maybe some of the advice in this guide could help out.

hjhart commented 2 years ago

@jcoyne @pinzonjulian We are starting to use ViewComponent closer to form_for (or form_with) and have hit a bit of a snag specifically with testing. Here is a basic example of how we might be passing around form objects, and how I would hope to test the component.

# frozen_string_literal: true

class AdditionalChildComponent < ViewComponent::Base
  attr_reader :form_context, :child

  delegate :styles, to: :helpers

  def initialize(form_context:, child:)
    @form_context = form_context
    @child = child
  end
end
<%= form_with model: @additional_child, scope: :account do |f| %>
      <%= render(AdditionalChildComponent.new(form_context: ff, child: @additional_child)) %>
<% end %>

And a snippet of additional_child_component.html.erb to see example usage.

  <%= form_context.text_field :first_name, class: styles.text_field, id: "account_children_attributes_first_name_#{index}", name: "account[children][][first_name]" %>

The problem is that I can't seem to construct a ActionView::Helpers::FormBuilder object to pass in to the Component, seeing as we only generate those contexts within views. This feels super contextual, and not even specifically a view_component issue, but I found this github issue and thought either of you might have some guidance on how you're using these.

Also, hopefully this might guide what documentation might be helpful to folks. Thanks in advance.

ohrite commented 1 year ago

@hjhart :wave: I'm sure you've solved this before, but I wanted to chime in and share that we're testing using a FormBuilder instance. The setup is a little clunky! It requires an ActionView::Base instance, which is a little off the beaten path:

RSpec.describe TextFieldComponent do 
  subject(:rendered_component) { render_inline(component) }

  let(:view) { ActionView::Base.empty }
  let(:store) { FactoryBot.create(:store, name: "Unincorporated Taco Bell or Pizza Hut") }
  let(:form) { ActionView::Helpers::FormBuilder.new(:store, store, view, {}) }
  let(:component) { described_class.new(form: form, attribute: :name) }

  it { is_expected.to have_field("Name", with: "Unincorporated Taco Bell or Pizza Hut") }
end
joshmfrankel commented 1 year ago

@ohrite's answer works well for ViewComponent previews when accepting ActionView::Helpers::FormBuilder (form_with) objects as arguments

class MyComponentPreview < ViewComponent::Preview
  def default
    view = ActionView::Base.empty
    object = SomeModel.first
    object_name = :some_model
    form = ActionView::Helpers::FormBuilder.new(object_name, object, view, {})
    render MyComponent(form: form)
  end
end
joeldrapper commented 1 year ago

@joelhawksley is there an issue describing the specific problems with using Rails form helpers in ViewComponent? I’ve been able to get form_with and form_for to work in Phlex, and I expect it would be even easier to get them working in an ERB-based view so I might be able to help.

Spone commented 1 year ago

@joeldrapper this issue is pretty old. All issues I've been experiencing with VC + forms have been solved by the recent compatibility module (https://github.com/ViewComponent/view_component/pull/1650).

We've also been able to create a VC-based form builder at https://github.com/pantographe/view_component-form