How are you all handling complex JavaScript components?

The thing that appeals to me about Hotwire is that it simplifies a lot of stuff. Less back-end code to serve responses for less front-end code. That’s nice! But, where I gain simplicity in one area, I feel like I am losing simplicity in another area - namely, client-side components. With frameworks like Angular, packaging up a component is a “known quantity”. You get to package your HTML and your scoped CSS and your JavaScript all in the same place, which is lovely. When it comes to Hotwire, I am not sure how you all best go about achieving similar things?

The canonical example for this might be a “fancy select menu.” In Angular, I could just create a custom component:

<my-select (change)="doSomething()">
    My Default Text
</my-select>

This <my-select> component will likely render-out some HTML that wraps a native <select> in a DIV and hides it (via opacity); and then has event-bindings for rendering the “selected option” in some embedded markup.

When I’m just using Hotwire, I’m not sure how I would do this? One thought would be to create a server-side template helper that is something like:

helper.fancySelect( ... )

… which would turn around and generate the HTML for the select and bind some Stimulus controller.

Looking to see what people are doing. Especially about managing CSS as the app grows (something that is not Tailwind CSS).

1 Like

You might want to look at the view_component gem to package up these feature-centric reusable elements. That gives you a convention-driven way to compose across the stack, putting the Ruby, CSS, JS, and [your template system of choice] together in an easily-found way.

I don’t use this much yet, but I do have a lot of examples of where I could in one of my projects. These patterns have developed over time, and like Rails itself, after a certain point you want to extract them into a “macro” that you don’t have to rewrite over and over (or copy and paste, which is how I have managed it so far). I’m in the midst of a big rewrite on the core project, and I hope to use this to pull some of my most heavily used patterns into a single element I can just plop into the page where they’re needed.

Walter

1 Like

Thank you, I will take a look at this :slight_smile:

erb partials still work great for this.

<% render "shared/components/fancy_select", options:, value:, label: %>
<%# shared/components/fancy_select %>
<%
options ||= []
value ||= nil
label ||= "" 
%>

<div class="fancy-select">
  <label><%= label %></label>
  <%# select tag goes here %>
</div>

For a full-featured example, here’s one from the Bullet Train project, its super_select partial which wraps select2. It reuses other partials, in particular the field partial which takes care of wrapping a label and a field. And here’s its stimulus controller. While it uses Tailwind, it still needs overrides for the select2 styles.

Disclaimer: I’ve been contracting on the Bullet Train project and worked on this super_select partial a bit.

1 Like

Ok cool, so there’s no real “magic” here (in a good way). Meaning, this is kind of doing what I sort of expected it would be doing (JS files in one place, CSS files in another, Stimulus controller “name” just hard-coded). To be clear, I like not having “magic”. I think this might be the way that I end-up doing it. So, thanks for pointing me in the direction that this is not an unreasonable approach.

Yep, as predictable and everything-in-its-place as you can get it.

1 Like

I 2nd the approach of using hotwire with view_components.

I’ll look closer. I’m not actually using Ruby on Rails, so whatever the View Component stuff is doing, I’ll have to lift-and-shift into my programming context.

@christian1 yeah, this is the thing I keep coming back to in my mind. There are things that Hotwire does well; and, there are things that I think a more component-oriented framework (like Svelte, like Angular) do really well. And, if I’m going to end up using one of those to build some of the UI, then is it really worth it to build the UI in two different ways? Or, should I just commit to something like Angular?

I’m trying to write-out my thoughts on the matter as this feels like something I need to decide before I start my next side-project.

No one has to read this - but, this is me trying to figure out how to proceed on my next project: On Starting A Side-Project: Hotwire vs. Angular

Part of me feels a strong desire to use Hotwire. Part of me also thinks I’ll be able to more effectively build with Angular.

Rails-7, from my point of view, is a big step, but very young.

It’s funny you mention that because I only recently started to look into Hotwire (January 2023), and I keep coming across posts about stuff that wasn’t working in like 2021, but has since been fixed. And, I keep feeling like I got in at the right time, right after a lot of early problems have been fixed. But, it does feel like a lot of things are still being fleshed-out.

I’m gonna keep experimenting with it; not sure where I’ll land just yet.

My view is that Rails 7 could (or should!) be so much more successful. See issue 47991, under rails, on github.

Ember, Angular or React have, in my opinion, peaked. Stefan Buhrmester also showed an interesting way to build svelte SPA apps with rails and inertia. But I found that the single page applications are overtaken. At least for my use cases. Biggest argument is a comprehensive testing and saving a lot of time. And I am a big fan of full stack development.

With Turbo and Stimulus there are not many cases left where you really need intensive Javascript. But for these rare cases I found Vite, Svelte and Capybara to be the perfect combination to fill this gap.

The biggest time saver is the Vite HMR! Check out my tutorial on dev-to (sorry, discuss.hotwired restricts me from placing links there) for details. You can leave a JavaScript based popover open while you style it in the IDE and Vite will instantly update the styles while the popover remains open!

The reason for Svelte is its simplicity! Its a powerful tool and treated as a competitior of react, but so easy and intuitive to learn. As a full stack developer you have enough to learn :slight_smile:

I, too, am a fan of the full stack. On the server, I use ColdFusion. So, this was originally one of the biggest draws to Hotwire—the promise to not have to go full-stack JavaScript. I do love JavaScript; but, I love it on the front-end much more than on the back-end.

There is a certain irony, though, about HMR (Hot Module Reloading) and Hotwire. Part of the big selling point of HMR originally was that you don’t have to recreate all the state on the client-side when you make changes. But, with Hotwire, there is much less state to deal with. So, HMR is not that much different than just refreshing the page.

I’ve heard many good things about Svelte, but have not looked into it yet. It’s on the list - but like you said, there’s so much to learn / know these days.

HMR: For changes to javascript, as you say, it makes no difference because the code has to be re-run. The advantage is pushing style changes without re-running javascript.

The other frameworks: Vue, Angular, Ember, React: Ember originally came from the house of Rails! It seems that Angular has become the most effective, but React is the clear leader, see: npm trends. And it has the biggest ecosystem of modules.

React is also likely to be chosen for Rails. Why is that? From what I have heard, especially from developers that I know and that use it, the most common reason is: It integrates best.

I have found that Svelte integrates better than all of theese and has the better technology.

I present: Version 3 of my new gem.

Aside the title of this discussion, “handling complex JavaScript components”, it handles a lot of stuff around and many cumbersome details where discussed here. When the turbo part really uses its potential, and thats big, the remaining part for “complex” JavaScript components gets smaller. You can, within the controller, write things like:

    action_to_me(
      'add_css_class',
      '#colored-element',
      'background-red'
    )

This does not require a partial or a template. This example has turbo_power included.

and test it, by rspec request test, with:

assert_channel_to_me(
   'colored-element', 
   action: 'add_css_class'
) { |args| args.last == 'background-red' }

Of course, it cannot replace system tests like Capybara altogether, but: reduce them massively.

and much more :slight_smile:

In my team we are currently using hotwire + stimulusJS + view components to be able to implement “fullstack components”.

I strongly recommend view components though it is not necessary.
When hotwire is not enough and we need client side behavior we use stimulusJS.
For these components it is important that all code related to the component lives within one place, so our folder structure is like this:

modal
  - component.rb 
  - component.html.erb
  - controller.js
select
  - component.rb 
  - component.html.erb
  - controller.js
dropdown
  - component.rb 
  - component.html.erb
  - controller.js

component.rb and component.html.erb are view component related, but could just as well be partials, and controller.js is a stimulus controller that only serves that component.

We have no scoped CSS because we are using tailwindCSS, but if you choose to use view components they have an alternative for scoped CSS as well.

Evil Martians have two great articles regarding view components that I strongly suggest:

And finally, when it comes to testing, you can unit test view components as described by the doc itself, and use Capybara to test complex components that require client/server behavior.

Hope this helps. :grinning:

1 Like

I’ll try to take a look at all this stuff, thanks y’all!

It’s interesting to see CSS/JS right alongside the RUBY code. I’m not used to seeing that. But then again, in a SPA context, this would all be separate. It’s so hard to divorce myself from the SPA-based thinking.

We’re using Rails 7 w/ Turbo + Stimulus. Like others posting in this thread, we’re finding view components (from GH) to be essential to keeping highly cohesive things together. It produces a much more readable and maintainable system that oodles of partials throughout. Using Evil Martians gem to further enhance the sidecar experience (locating the view component rb alongside the slim/erb in the component directory) is also nice.

Re: the original question of “how are we handling complex JS components”?.. we’re trying to align with the vision of unobtrusive JS and sprinkling abstract/utility stimulus controllers as much as possible. I’m currently working on a fairly hi-fi page. If I go with that strategy, I’ll end up with 7 or 8 different stimulus controllers controlling things all over the page. I’m hestitating… looking for a cleaner design. I don’t really want to eject out of Hotwire and deliver this with a React component, though, b/c the page is composed of building blocks used elsewhere in the app (really don’t want to duplicate those components into 2 ecosystems).

TL;DR Still looking for a great solution. Likely will make a stimulus controller for the page that brings together what 3 or 4 separate stimulus controllers might have done

1 Like

Yes we are facing similar issues here. I believe it’s just a matter of time until we have to use vuejs or another client-side library to handle complex pages.