Wanted: Experience Reports #15
Replies: 47 comments 6 replies
-
We've started using Thank you so much for kickstarting this - it feels to me that IssuesWe've noticed a few issues in our application (Rails 5.2) that I'll start by listing here. We've found some workarounds but I'm not sure if they are the best way to fix them - I'm happy to create a PR/issue if you think they have potential! Unable to render forms/button_to from a componentRendering a form/button_to from a component raises an error Unable to render partials and use
|
Beta Was this translation helpful? Give feedback.
-
First; I love this so much. Had the exact same experience of coming back from React and "missing" the component way to structure things. And testing – this is so much better ❤️ I'll second the experiences of @metade – in development, changes aren't always picked up. Perhaps it could be solved as easy as disabling template compilation in development (which I think is happening?) |
Beta Was this translation helpful? Give feedback.
-
@metade @mikker thank you SO much for your experience reports. This is exactly the kind of feedback we are looking for. The local reloading issue definitely needs to be addressed. I've filed an issue here: #19 and have started working on a fix. @metade generally, it sounds like we need more of the context Rails provides views when being rendered. I'd prefer to come up with a solution that allows for components to not be coupled to the request, allowing them to be used outside of the request path (such as in jobs or mailers). However, we'll probably need to at least inject a dummy controller for things like rendering forms. This is an issue that has come up internally that we haven't really dug into yet, but plan to look at soon. For now, your solution of injecting the controller and view flow context seems directionally correct. |
Beta Was this translation helpful? Give feedback.
-
Took AV::C for a test drive today. Here's some freeform impressions. I immediately started rearranging the innards. The naming conventions blew some fuses. The first was renaming The testing story is really nice. Every large team & large application is crying out for testable view objects with programmatic quality control. This is AV::C's biggest win. The collection rendering story isn't great. Collection partials were 25x faster over component iteration in my testing. However, I can't see a way to achieve a flyweight render function without dumping validations. My biggest concern: there's little-to-no convention-over-configuration going on. One of the stated benefits of AV::C is in reducing conditionals, and yet there's conditionals in the demo because only explicit class selection is supported. In the end I still really want to write To me, right now, it feels like a viable OO replacement for helpers and for value objects, but not (yet) for rendering entity models and collections. I'm not clear on whether that was the intention or not. More broadly I've got a concern that AV::C is not a terribly enthusiastic participant in the Rails framework. |
Beta Was this translation helpful? Give feedback.
-
@inopinatus thank you for the thorough report! You raise several great points that definitely should be incorporated into Naming conventionI like the idea of having
|
Beta Was this translation helpful? Give feedback.
-
Hi guys! I just started using this and I absolutely love it. I was wondering if you've thought of adding a generator? I know it's a simple class but it would help a lot
Let me know what you think. |
Beta Was this translation helpful? Give feedback.
-
Hi all! Thanks so much for putting this together! I'm excited to see this moving forward and gaining some momentum! We are evaluating different approaches for components right now, and it's basically down to AV:C or https://github.com/jensljungblad/components (which I really like, but doesn't appear to be active; I'll refer to it as Here are some initial thoughts from my perspective after having done a PoC with both frameworks.
Element ExamplesBelow are a couple of examples of how we're using Elements. In the second example, you can see how we're passing some content in the This is nice because it creates less of a burden on developers to know in what order to declare things; which is especially troublesome when you have lots of different options. = component('lumen/breadcrumb', css: 'large') do |breadcrumb|
- breadcrumb.section do
%i.ui.home.icon
Home
- breadcrumb.section divider: true
- breadcrumb.section css: 'active', href: '/' do
Index = component('lumen/message', dismissable: true) do |message|
- message.header do
Dismissable message!
Here is a dismissable message! I'm sure I'll have more thoughts as I progress, but wanted to shoot these your way so we could bounce some ideas around! Thanks again! P.S. Sorry, I hit Shift-Enter which submitted my comment before I was finished 🤦♂ |
Beta Was this translation helpful? Give feedback.
-
@topherfangio Thanks for checking out https://github.com/jensljungblad/components. I always thought with Ruby we should be able to one-up React :) If @joelhawksley I was checking out the sample application and I've got a question! <%= render(Issues::Badge, state: issue.state.to_sym) %> This passes a <%= render(Primer::State, color: STATES[state][:color], title: "Status: #{state.to_s.titleize}") do %>
<%= octicon(STATES[state][:octicon_name]) %> <%= STATES[state][:label] %>
<% end %> Why not have methods on the component that fetches the |
Beta Was this translation helpful? Give feedback.
-
@jensljungblad So, that should be totally possible. When you create the component, you can define any methods you wish, and they can definitely be based off of any instance variables. So something like this should be totally valid: class Issues::BadgeComponent < ApplicationComponent
COLORS = [open: :green, merged: :purple, closed: :red]
validates :state, presence: true
def initialize(state:)
@state = state
end
def color
COLORS[@state]
end
private
attr_reader :state
end |
Beta Was this translation helpful? Give feedback.
-
@topherfangio How would you access them from the template? |
Beta Was this translation helpful? Give feedback.
-
@jensljungblad they're available directly, like |
Beta Was this translation helpful? Give feedback.
-
Strange, I got errors when I tried that. I'll try again :) Ah, I was apparently using version 1.2.0, which gave me a method not defined error. Updating to 1.2.1 fixed the problem! |
Beta Was this translation helpful? Give feedback.
-
I have created a gist that shows how to get AV:C working with the DSL provided by https://github.com/jensljungblad/components! I have some notes/thoughts at the end about some issues I ran into while building this. The biggest one is that debugging is very difficult because it seems to die on the https://gist.github.com/topherfangio/76615f32f7ebb25e3caa03741cb13a59 It's usuable as-is, but I feel like there is a lot of room for improvement. Would love y'all's feedback! Thanks! |
Beta Was this translation helpful? Give feedback.
-
It doesn't work with:
We went down that path of storing the view context and setting certain instance variables so that various |
Beta Was this translation helpful? Give feedback.
-
#28 with a little tweak to get it working with So now after actually using it... It causes a proliferation of classes and files in my codebase. Consider the example of a PostsController, and the feasible components that go along with basic crud: index, index_item, new, edit, show, form. That's 10 files and 5 new classes. 4 out of 5 of the classes are identical except for the names. I would rather it be like controllers, i.e. "a controller has many actions", ala "a component has many templates". This is how Cells and Phoenix work as well, and I think it cuts down files/classes/module and makes code reuse/sharing easier. That means validation will have to support Fwiw, I think validations declared on the class is an antipattern (I think this about ActiveRecord also). class PostComponent < ApplicationComponent
attr_reader :post, :posts
def initialize(post: nil, posts: nil)
@post = post
@posts = posts
end
def index
validates_presence_of(:posts)
render :index
end
def show
validates_presence_of(:post)
render :post
end
end Using this more procedural approach, validation can be conditional using simple if statements, and grouped together for reuse via functions (methods). Otherwise, implementing conditional validation on the class becomes needlessly complex: class PostComponent < ApplicationComponent
validates_presence_of :post, if: :some_method
validates_presence_of :post, if: ->{ blah }
validates_presence_of :post, only: [:new, :show, :edit]
end |
Beta Was this translation helpful? Give feedback.
-
Is there any solution available for component scoped CSS? Whether that's some other dependency, or built in? |
Beta Was this translation helpful? Give feedback.
-
@joshleblanc not to my knowledge, no. It's on our radar, but not until later this year at the earliest. |
Beta Was this translation helpful? Give feedback.
-
@joelhawksley Thank you for collection rendering! It's awesome! |
Beta Was this translation helpful? Give feedback.
-
@rosendi don't thank me, thank @tclem! (and @jonspalmer / @rainerborene 😄 ) |
Beta Was this translation helpful? Give feedback.
-
@joshleblanc @joelhawksley what I am currently trying in combination with stimulus, is scoping on the So in the import './description_component.scss' and the css looks something like this: [data-controller="description-component"] {
background-color: greenyellow;
} If you don't need any javascript for the component, just using the import statement is fine as long you use webpack for css. |
Beta Was this translation helpful? Give feedback.
-
@joelhawksley I am a n00b in Ruby on Rails and found this while looking for how can I add Jekyll-style rendering of .md files to this website. Over initial scanning of the text, I have no idea what a component is, other that it should probably meant to be used in I would really appreciate a n00bs tutorial for people who just started with Rails and followed its tutorial to build their first |
Beta Was this translation helpful? Give feedback.
-
Hey @abitrolly ! On using it outside of RailsI believe it's not possible at the moment (and probably not in the future). The objective of this gem is meant to work as a first class citizen in Rails and it depends on some of Rails' core modules. See the docs for a list of other Ruby libraries that probably don't depend on Rails for this. What's a component?You can check the docs here. In short: View component is a way to have a ruby object handle all the logic for rendering a piece of html just like JS handles all the logic to render html in React. Different from react, you have 2 files instead of 1: your template and the ruby file. All the logic you would add in your view would now be handled by the ruby object. Starting out with Rails?View-component is at the bleeding edge of what you can do in terms of rendering html to a page following Rails' conventions as close as possible so there aren't going to be many (if any) examples on the web right now to help you other that what you can find here. Maybe you can check out other, more mature implementations here that offer similar features and probably don't depend on Rails. Taking a shot at your questionsOn routing:I'm guessing you're comparing View-component to React and how it uses routes to render components. This uses a totally different architecture. You navigate to a page with a regular http visit and then, inside the response, you render one or multiple components in your view. For example, if you have a Common config:Not sure what you mean by this. Most stuff in vanilla Rails don't need much configuration. Everything works out of the box and this gem is supposed to be that way too. How are they subclassed?You can create classes that inherit from On rendering markdownCheckout redcarpet here Hope this helps! |
Beta Was this translation helpful? Give feedback.
-
@abitrolly welcome! @pinzonjulian has done a better job responding that I ever could! Thank you Julián! |
Beta Was this translation helpful? Give feedback.
-
@pinzonjulian thanks! Help to set the limits. I have little experience in JS, so don't know what React is. From what I know, React looks similar to game loop, where each frame is a iteration - data preparation, rendering, handling of input events. But maybe that's Flux. Not sure. Among the Ruby on Rails tutorial I found this one to be the best - http://installfest.railsbridge.org/intro-to-rails/intro-to-rails - short practical steps with minimal text. By Summarizing. I can not build an isolated "component" that just renders markdown to refer to it directly from |
Beta Was this translation helpful? Give feedback.
-
I wonder if there's a way to integrate StimulusReflex into view component. https://docs.stimulusreflex.com/quickstart#call-reflex-methods-on-the-server-without-any-javascript If your view component was a stimulus reflex controller itself, then it could obscure the line between front and backend. |
Beta Was this translation helpful? Give feedback.
-
Firstly, hi 👋, and this is awesome 🔥. I really appreciate the extra effort y'all are doing to make this have third-party (and hopefully first-party) support in Rails. Rails lack of view objects for partials has made me crazy since it came out, and this is the best solution I've seen yet. The below is written from the perspective of a team that already has a large Vue-based application (which we are keeping), but also needs to add some other complex reactive features to the server-rendered part of the site (like for admin and customer billing, etc.). As far as I can tell, ViewComponent supports most of our use case, but there's a few major missing features for our large app needs. Multiple views per component Allowing ViewComponent to use one component object but support multiple templates is really valuable. This is not an uncommon situation for our front end because we have a lot of "summary" areas with mostly the same data but very different display modes. This would be a way that ViewComponent could be better than how current front end component-based frameworks work. For example, we're using a complex but mostly self-isolated Vue component as a spike test for ViewComponent, called Here's how we imagine this would look with ViewComponent: <%= render(PricingSummaryComponent.new(template: 'vertical')) %> class PricingSummaryComponent < ApplicationComponent
def initialize(template:)
@template = template
end
# overrides parent method to specify a variant
def template_variant
# sample erb code above would load:
# pricing_summary_component.vertical.html.erb
#
# or if `template_variant` returns nil (the default), it would load
# pricing_summary_component.html.erb
#
# else if template isn't found, it's an error of course
@template
end
end First-class subfolder support Along with multiple template variants, our setup would greatly benefit from subfolder support. I understand that the GitHub use case doesn't currently require subfolders, but I'm certain this will be an issue for other people too. I like the idea of following the order of how Rails looks for views, and first checking With our test component, we already have a lot of support files, and I expect it to grow as third-party support of ViewComponent expands:
For our
Reducing useless mini sub-components As @topherfangio described above, I think there's one feature of One of the annoying things about React and Vue is all of the "mini" components that you end up with that aren't actually reusable in any other context, like a ViewComponent being able to specify multiple bodies with Here's a simplified example of this use case: class MultiSelectListComponent < ApplicationComponent
# add content area options
with_content_areas :header, item: { multiple: true } <!-- usage in a view -->
<%= render(MultiSelectListComponent.new) do |c| %>
<% c.with(:header) do %>
<i class="fas fa-temperature-high"></i>
Permission List
<% end %>
<% permissions.each do |permission|
<% c.with(:item) do %>
<input type="checkbox" ...>
<% if permission.readwrite? %>
<i class="fas fa-lock-open"></i>
<% else %>
<i class="fas fa-lock"></i>
<% end %>
<%= permission_name %>
<% end %>
<% end %>
<% end %> <!-- multiSelect_list_component.html.erb -->
<div data-controller="multiSelect-list">
<div><%= header %></div>
<ul>
<!-- Proposal - with multiple bodies: AWESOME 🎉-->
<% items.each_with_index do |item, index| %>
<li class="multiSelect" data-target="multiSelect-list.item" data-index="<%= index %>">
<%= item %>
</li>
<% end %>
<!-- Current - without multiple bodies: YUCK 💩 -->
<!-- requires a pointless separate component without multi-body support :( -->
<%= render(MultiSelectListItemComponent.with_collection(items) %>
</ul>
</div> Allowing component.with() to take options Building on "reducing mini sub-components", this came up while building a nav component. In this case, the nav item needs to know if it's active or not so it can change how the item is displayed. <%= render(NavComponent.new) do |c| %>
<% pages.each do |page| %>
<!-- need to pass in arguments here -->
<% c.with(:item, active: current_page?(page.path)) do %>
<%= page_icon(page) %>
<%= page.name %>
<% end %>
<% end %>
<% end %> <!-- nav_component.html.erb -->
...
<% items.each do |item, args| %>
<!-- this would be silly to make an entire component for -->
<li class="nav-item <%= 'active' if args[:active] %>">
<%= item %>
</li>
<% end %>
... Caching I believe this is covered in #234 but this is a really important one for us. Thank you for reading and for this awesome gem! I am happy to go into more detail if something wasn't clear. |
Beta Was this translation helpful? Give feedback.
-
@bbugh Great feedback 💯 Also mostly inline with my experience as we get deeper into using ViewComponents in anger. Hoping you can help explain your use cases. I'm confident we can address many of the ideas you bring up.
I've faced similar situations. I switch between thinking the multiple views would be an AWESOME feature and considering it an anti-pattern better solved with composition of inheritance 😀 That said I think we should consider it. It would be super helpful to have some concrete examples to look at and some sketches of what an ideal API would be to supply the variants. It's important to note that all the 'variations' of the views today get "compiled" into methods on the class so we'd likely need an API that statically defines the fixed list of views that were available as well as the api to choose them at render time.
This is subtle given how Ruby handles namespaces. You can make it work like this today by name spacing your components. In your example I'd love there to be a solution like you propose - it would be very natural. I'm not an expert in how file loading/class resolution happens in Ruby/Rails. Clearly Rails is configured so that folders all subfolders of WRT Stories you can already configure your stories to be anywhere you like: https://github.com/jonspalmer/view_component_storybook#configuration config.view_component_storybook.stories_path = Rails.root.join("app/components")
Multiple bodies for content-areas should be pretty achievable. I'd love your opinion on the API. Your suggestion of supplying it as part of <% c.with(:item, append: true) %> Reason being that I wonder if there are use cases where you want the ability to append and replace dynamically or is a given content_area always in one mode or the other? I could also imagine we offer both - What do you think? I'd love more example use cases to validate the design against.
This ask is captured in #285. Good timing because we have some attempts at providing this #323. I'm a bit confused by your example NavComponent.
<%= render(NavComponent.new) do |c| %>
<% pages.each do |page| %>
<!-- need to pass in arguments here -->
<% c.with(:item) do %>
<li class="nav-item <%= 'active' if current_page?(page.path) %>">
<%= page_icon(page) %>
<%= page.name %>
</li>
<% end %>
<% end %>
<% end %> <!-- nav_component.html.erb -->
...
<%= item %>
... I'd like to collect more use-cases for passing arguments to content areas. It seems like |
Beta Was this translation helpful? Give feedback.
-
Hey, thanks for the reply! I appreciate how engaged y'all are in making this as awesome as possible. It's refreshing from some of the other open source projects I've worked with lately. 😀
I think you're right, maybe it can go either way. I'm not sure how often our specific use case of multiple templates with one view model would come up for other people. We were thinking of it in the way that DHH's caching article describes multiple variations of their components (under "Thou shall share a cache between pages"). In their case it's one template modified by CSS, in our case it's multiple templates just because of differently shaped HTML requirements, but basically the same thing. E.g. the On the other hand, I think that separate component classes (even if they're a one line Given that Component object composition already supports this without new features and the existing solution is probably better than our proposal, this seems like something that could be waited on.
Ahh, that isn't great, but you're right given Rails' file loading that might be the best option. I think I remember seeing that someone else solved it this way already. Sorry for repeating this!
That's a cool idea, but I think it makes the internal implementation more complicated and won't work for the given example. If you're saying <!--
Header shouldn't be appendable but we have to handle the append case
-->
<% Array.wrap(header).each do |header_content| %>
<div><%= header %></div>
<% end %> Or, if you're saying <!-- multi_select_list_component.html.erb -->
<% items.each_with_index do |item, index| %>
<li class="multiSelect" data-target="multiSelect-list.item" data-index="<%= index %>">
<%= item %>
</li>
<% end %> I personally think it's a better API to let the component define whether an input is a list or not, rather than letting the consumer pass in anything as a list. I think this would best be explicitly defined in the
To be honest, I'm a bit confused by your questions, so I'll do my best to explain!
If I understand what you mean by this, yes, that is correct - this is based on the requirement that
That's an easy one to answer - because the consumer of the component shouldn't need to know the internals of how the component is rendered to use it! That's very error prone, hard to refactor/enhance and can get quite complicated when you get Stimulus involved. Let's make it real-world complicated so you can see why you wouldn't do this. Below is a reusable timeline component. We use something like this in multiple ways - a history of project updates, a list of recent user comments, a team's onboarding of new users, etc. Each of these is backed by a list of many different models or polymorphic lists, so we can't tie the TimelineComponent to any particular model. Using your method without arguments, the TimelineComponent consumers would have to do this: <%= render(TimelineComponent.new(option1: X, option2: Y) do |c| %>
<% activities.each_with_index do |activity, index| %>
<% c.with(:item) do %>
<div class="timeline-item">
<div class="timeline-icon">
<%= fa_icon(activity.icon) %>
</div>
<div class="vertical-line"></div>
<div class="activity-description"
data-target="timeline.item"
data-index="<%= index %>"
>
<%= activity.name %>- <%= time_ago_in_words(activity.created_at) %>
</div>
</div>
<% end %>
<% end %>
<% end %> That unnecessarily exposes a TON about internals of the TimelineComponent to the consumer, when all the component needs beyond the By allowing <%= render(TimelineComponent.new) do |c| %>
<% activities.each do |activity| %>
<% c.with(:item, icon: activity.icon) do %>
<%= activity.name %> - <%= time_ago_in_words(activity.created_at) %>
<% end %>
<% end %>
<% end %> I hope that example speaks for itself! The consumer doesn't need to care at all about how the timeline renders, just what input it takes. Now imagine that this TimelineComponent came from a third party gem. Knowing all of the internals of how it works would be a mess!
If I understand what you mean by this, I would have to argue the other way, that it's very common, especially with reusable input-agnostic components. In the above example, the I hope that I answered everything! Happy to share more as needed. If you want to continue the discussion in individual issues, feel free to tag me there with questions. |
Beta Was this translation helpful? Give feedback.
-
@bbugh Thank's for the details and patient response. I've broken the multiple content-area conversation into its own issue #325. Please keep the awesome feedback coming. You clearly have some deep practical experience with ViewComponents. Real-world problems and their solutions are crucial for us deliver the next set of features. Keep it coming. 🎉 |
Beta Was this translation helpful? Give feedback.
-
We started using view_components a couple of months ago and it seems a better solution than partials just for the sake of having a well defined interface that other developers can use. The only "issue" we found is that we can't figure out how to test view_components that take form_for object... we have a component like this: module Components
class ImageUploader < ViewComponent::Base
def initialize(form:, attribute:, input_options: nil)
@form = form
@attribute = attribute
@input_options = input_options
end
end Which is responsible for generating a complex html where user can upload their images/crop them/delete them... Basically: How to pass form objects inside unit tests? :D |
Beta Was this translation helpful? Give feedback.
-
👋 thanks for checking out
actionview-component
!In sharing this gem with the Rails community, we are looking to hear about your experiences with using
ActionView::Component
in your applications.Please let us know what works well, what doesn't, and what you think is missing ❤️
If you'd rather provide feedback privately, feel free to contact me at joelhawksley@github.com.
Beta Was this translation helpful? Give feedback.
All reactions