How are you all handling complex JavaScript components?

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.

A few updates below. For context, the page we’re working on is a search form, allowing the user to cherry pick result items to include in their project/whatever.

We had several stimulus controllers managing the search form itself:

  • to verify that required fields were populated before enabling the submit button
  • to verify that invalid fields were changed before enabling the submit button
  • to perform a logic operation (AND) on the results of the 2 said controllers (required fields and valid fields)
  • to clear the error state of an invalid field in response to a change event on that field

We ended up consolidating much of this into a single “FormController”.

Also, we noticed that between these controllers and others, there was a pattern emerging:

  • monitor a group of input fields (eg input & change events)
  • form a conclusion about the state of something else based on the state of those inputs
  • affect that something else

For example (a controller ensuring that required fields are populated before being allowed to submit a form):

  • monitor inputs with the .required class
  • detect a blank value of any of those elements
  • disable the submit button if any required inputs were blank; enable it otherwise.

Or this one (a badge displaying the count of active filters in a section)

  • monitor inputs under a collapsing section header (use targets for this one)
  • count the number of non-blank elements
  • set the innerText of an outputTarget to equal the count

We extracted a map-reduce style mix-in that can be composed into stimulus controllers, and configured with callback functions for:

  • input (return elements to monitor)
  • map (transform element into information of interest)
  • reduce (combine the array of values of interest into a final answer)
  • output (change the state of other elements based on the final answer)

Here’s an example of it being used to implement the required fields controller logic:

useMapReduce(this, {
      input: this.requiredFields, // searches the form.elements for fields having `required` class
      map: elementValue, // get the value of each element
      reduce: this.requiredConditionsSatisfied, // yield true if all values are present?
      output: requiredConditionsSatisfied => setButtonEnabled(this.outputTarget, requiredConditionsSatisfied) 
    })

This ended up reducing a lot of our boiler-plate.

Along with carefully choosing a more course-grained stimulus controller (a single form controller instead of 3 or 4 sewn together), the team is much happier with the maintenance job remaining.

1 Like

The combination of Hotwire and the View Components gem has been a great combination for us. Makes creating simple reusable components easy to build/maintain.