How are you all handling complex JavaScript components?

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.