Heading image for post: Building a (Very) Simple Responsive Search with Rails & Stimulus

Building a (Very) Simple Responsive Search with Rails & Stimulus

Profile picture of Jack Rosa

Here's a simple and responsive search form I put together for a recent side project, using Hotwire's Stimulus framework and Rails with Turbo.

The Form

Here's how the search input looks. It's a simple search form connected to a Stimulus controller. The input triggers the search function upon every input event. The debounce logic occurs in the Stimulus controller to make sure not too many requests are made to the server:

<%= form_with url: items_path, method: :get,
    data: { controller: "search", turbo_frame: "items_list" } do |f| %>
  <%= f.text_field :query,
      placeholder: "Search...",
      data: {
        search_target: "input",
        action: "input->search#search"
      } %>
  <button type="button"
      class="hidden"
      data-search-target="clearButton"
      data-action="click->search#clear">
    ×
  </button>
<% end %>

There's an additional feature here: a button which appears in the search field whenever the field has a value. The button has a data-action attribute pointing to the clear action. If the button is clicked, the input value is cleared out.

The Stimulus Controller

All we need to do here is submit the form with a debounce timer, handle the action for clicking the clear input button, and handle hiding the clear button if the input field has a value or not.

export default class extends Controller {
  static targets = ["input", "clearButton"]

  connect() {
    this.toggleClear()
  }

  search() {
    this.toggleClear()
    clearTimeout(this.timeout)

    this.timeout = setTimeout(() => {
      this.element.requestSubmit()
    }, 300)
  }

  clear() {
    this.inputTarget.value = ""
    this.toggleClear()
    this.element.requestSubmit()
  }

  toggleClear() {
    if (this.inputTarget.value.length > 0) {
      this.clearButtonTarget.classList.remove("hidden")
    } else {
      this.clearButtonTarget.classList.add("hidden")
    }
  }
}

The Turbo Frame

You'll notice the form targets an items_list turbo frame, which contains your search results in the view. When the Stimulus controller submits the form, the content inside this frame gets replaced with the new search results. This is a pretty simple way to update the search results without full page reloads. I also wanted to avoid turbo streams for this implementation, because I find them a tad awkward sometimes.

<%= turbo_frame_tag "items_list" do %>
  <% @items.each do |item| %>
    <%= render item %>
  <% end %>
<% end %>

The Rails Controller

Standard index action with optional query filtering. Works with or without search params:

def index
  # you will probably paginate your results
  if params[:query].present?
    @items = Item.where("name LIKE ?", "%#{params[:query]}%")
  else
    @items = Item.all
  end
end

Conclusion

This is a nice simple starting point to build out some more complicated search features from. I'd recommend using a pagination gem like pagy to handle the search results while keep your view manageable.

More posts about rails Hotwire Turbo Stimulus

  • Adobe logo
  • Barnes and noble logo
  • Aetna logo
  • Vanderbilt university logo
  • Ericsson logo

We're proud to have launched hundreds of products for clients such as LensRentals.com, Engine Yard, Verisign, ParkWhiz, and Regions Bank, to name a few.

Let's talk about your project