# Live search without the entire page reload, just the partial

**URL:** <https://discuss.hotwired.dev/t/live-search-without-the-entire-page-reload-just-the-partial/1339>\
**Category:** General\
**Created:** [August 18, 2020, 7:01pm UTC](https://discuss.hotwired.dev/t/live-search-without-the-entire-page-reload-just-the-partial/1339 "2020-08-18T19:01:33Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![SylarRuby](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/sylarruby/32/106_2.png) [@SylarRuby](https://discuss.hotwired.dev/u/SylarRuby)\
**Post date:** [August 18, 2020, 7:01pm UTC](https://discuss.hotwired.dev/t/live-search-without-the-entire-page-reload-just-the-partial/1339/1 "2020-08-18T19:01:33Z")

</div>

I’m wondering how Basecamp pulled this off without the use of JavaScript\*. I’m doing a live search but instead of showing a dropdown with results, I’m updating the page itself.

\*Javan says:

> [@Sharing HTML between Stimulus and Rails view](https://discuss.hotwired.dev/t/sharing-html-between-stimulus-and-rails-view/463/5):
>
> No, we don’t have any `.js.erb` files at Basecamp. The majority of our HTML is server-rendered using standard `.html.erb` templates.

Say what now? Ok. I’m using Turbolinks to make a GET request which passes a query string to the controller, updates the variable which renders the value:

```rb
# Controller
def index
  @customers = Customers.all
  @query = ''

  if params[:q].present?
    @customers = Customer.all.limit(4)
    @query = params[:q]
  end
end

```

```js
// Simulus controller
[..]

search(event) {
  const query = event.target.value.toLowerCase()
  Turbolinks.visit("/customers?q=" + query)
}

[..]

```

```erb
<!-- Filter section -->
<input 
  type="text"
  data-target="foo.searchinput"
  data-action="keyup->foo#search"
  placeholder="Search"
  value="<%= @query %>"
  autocomplete="off"
/>

<!-- The customers list -->
<%= render partial: 'list', locals: {customers: @customers} %>

```

The logic works but [here’s what I don’t get](https://github.com/turbolinks/turbolinks#building-your-turbolinks-application):

> Turbolinks is fast because it doesn’t reload the page when you follow a link.

What? The entire dom is being replaced. Ok, maybe this happens after I hit the controller. My issue is that the input focus is lost and if I do use js to add `autofocus`, the cursor goes back to the start of the value. Could someone show me how to get a live search going with the result updating the page while the input is still being focused? Possible? I’m coming from a js-only client (React).

---

<div class="post-metadata">

**Author:** ![welearnednothing](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/welearnednothing/32/502_2.png) [@welearnednothing](https://discuss.hotwired.dev/u/welearnednothing)\
**Post date:** [August 18, 2020, 11:16pm UTC](https://discuss.hotwired.dev/t/live-search-without-the-entire-page-reload-just-the-partial/1339/2 "2020-08-18T23:16:15Z")

</div>

Hey @SylarRuby!

You would just return a fully rendered HTML response with no layout and inject it into the page in a situation like this, rather than completely render a new page for Turbolinks to process. See [this recent post](https://discuss.hotwired.dev/t/fetching-a-partial-on-click/1297/4) for some more talk about remotely fetching some data and dropping it into the page. The gist of it is that in many scenarios it’s possible to have a single controller that can be reused across various use cases involving remotely fetching data. That controller could be as simple as:

**remote\_partial\_controller.js**

```auto
import { Controller } from 'stimulus'

export default class extends Controller {
  static targets = ['display']

  render(evt) {
    this.displayTarget.innerHTML = evt.detail[0].body.innerHTML
  }
}

```

For your situation, you would just have your input submit a remote form (the form will be remote by default in Rails) and configure an action that tells RemotePartialController to render the response.

Now, you may have additional logic necessary for the autocomplete search (for instance don’t perform a search until there are at least two characters). In your example, you’re using a FooController with a #search method for this. That controller and the RenderPartialController can happily live side by side here, but if it’s easier or makes more sense, you can easily just take the RenderPartialController#render method and move that into your FooController.

Also, in your FooController, you’ll want to submit the form via `Rails.fire(form, 'submit')` rather than call Turbolinks.visit. Then have an action that triggers `render` when a response is returned from the form submission.

I hope that makes sense, I’m happy to go into more depth if necessary. I unfortunately don’t have time to write up a full working example, but if that would be helpful I can whip one up later.

Regarding the quote:

> [@](#):
>
> Turbolinks is fast because it doesn’t reload the page when you follow a link.

it’s replacing the DOM inline (really just the body) - it’s not actually clearing the current page from memory and loading a new one, as would typically happen when you navigate from page to page in a non-SPA situation. This means it doesn’t have to re-parse any JS or CSS payloads. SPAs have this advantage, too, but SPAs are also tied to an API and you’re rendering a JSON payload on the backend and then rendering HTML on the frontend. With Turbolinks, you skip the JSON, render the HTML on the backend, and assuming you keep your backend fast you’ll realize some of the advantages of a SPA but typically with a lot less code and complexity by just writing code like a non-SPA website.

Hope this helps!

---

<div class="post-metadata">

**Author:** ![SylarRuby](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/sylarruby/32/106_2.png) [@SylarRuby](https://discuss.hotwired.dev/u/SylarRuby)\
**Post date:** [August 19, 2020, 5:38am UTC](https://discuss.hotwired.dev/t/live-search-without-the-entire-page-reload-just-the-partial/1339/3 "2020-08-19T05:38:18Z")

</div>

Wow. Thanks for the write up. I’ll dissect the information you gave me and see what I can come up with. If I’m stuck, or resolve it, I’ll come back.

Thanks, again.

---

<div class="post-metadata">

**Author:** ![SylarRuby](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/sylarruby/32/106_2.png) [@SylarRuby](https://discuss.hotwired.dev/u/SylarRuby)\
**Post date:** [August 19, 2020, 8:31am UTC](https://discuss.hotwired.dev/t/live-search-without-the-entire-page-reload-just-the-partial/1339/4 "2020-08-19T08:31:12Z")

</div>

Ok done that but the `innHTML` I see is the entire page. How to get the `@customers` only? My controller action is:

```auto
def filtered
  @customers = Customer.first
  respond_to do |format|
    format.js
  end
end

```

But `evt.detail[0].body.innerHTML` is the entire page.

---

<div class="post-metadata">

**Author:** ![welearnednothing](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/welearnednothing/32/502_2.png) [@welearnednothing](https://discuss.hotwired.dev/u/welearnednothing)\
**Post date:** [August 19, 2020, 7:24pm UTC](https://discuss.hotwired.dev/t/live-search-without-the-entire-page-reload-just-the-partial/1339/5 "2020-08-19T19:24:12Z")

</div>

Glad you’re making progress, @SylarRuby!

The rest of the page is coming from the layout, so you’ll want to set that to none. You can do this at a controller level, if none of your actions have layouts, or per action. It looks like you might have this in a `CustomersController` and likely need a layout for `#index`, `#show`, etc., so I’ll demonstrate at the action level. Note that because you’re not providing different response types (e.g. HTML, XML, JSON… you’re only returning HTML), there’s no real need to use `respond_to`, so I’ll simplify that with just a `render` call.

```auto
def filtered
  @customers = Customer.first
  render layout: false
end

```

That oughta do the trick!

Now, I will recommend that you actually break search out into its own controller rather than overload `CustomerController` with more methods. It leads to more controllers, but each with less and more focused responsibilities. [Here’s a nice write up](http://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/) about it, and it’s how Basecamp approaches things. Once I tried that approach, I never went back.

So with that approach, you might end up with something like:

**CustomerSearchController.rb**

```auto
class CustomerSearchController < ApplicationController
  layout false

  def index
    @customers = Customer.first
  end
end

```

Obviously, your actual search logic will be more involved, but it just keeps things ridiculously simple. Mix that with some simple Stimulus on the frontend and you have a nice auto-complete search with very little code.

Let us know when you get it working! 🤙🏼

---

<div class="post-metadata">

**Author:** ![SylarRuby](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/sylarruby/32/106_2.png) [@SylarRuby](https://discuss.hotwired.dev/u/SylarRuby)\
**Post date:** [August 19, 2020, 8:06pm UTC](https://discuss.hotwired.dev/t/live-search-without-the-entire-page-reload-just-the-partial/1339/6 "2020-08-19T20:06:01Z")

</div>

> [@welearnednothing](#):
>
> ```auto
> @customers = Customer.first
> render layout: false
> 
> ```

Thanks. I’ll “clean up” later but it’s not working at all. Here’s what I have (the input):

```erb
<form
  data-action="ajax:success->customertabs#result"
  data-target="customertabs.form" action="<%= foo_path %>" method="post" data-remote="true">
  <input 
    type="text"
    data-target="customertabs.searchinput"
    data-action="keyup->customertabs#search"
    placeholder="Search"
    class="fn_input"
    autocomplete="off"
    name="search"
    data-remote="true"
  />
  <input type="hidden" name="sort" value="{asn:1,db:123}" />
</form>

```

That is exactly what I have. Routes:

```rb
post '/foo', to: 'customers#foo', as: 'foo'

```

**Controller#action:**

```rb
def foo
  puts "You got me"
  @customers = Customer.all.limit(1)
  render layout: false
end

```

I do see the “You got me” logged so I’m hitting the action.

**Stimulus:**

```js
[..]
result(data) {
  console.log(data)
}

search(event) {
  const query = event.target.value.toLowerCase()
  console.log(query)
  Rails.fire(this.formTarget, 'submit')
}
[..]

```

When I console logged `data` I see two results: one with no “document” and one with “document”. The one with “document” returns the full html so Im guessing Im missing something? See image:

 ![Screenshot 2020-08-19 at 20.58.20](https://canada1.discourse-cdn.com/flex027/uploads/stimulusjs/original/1X/5957ba40701fef4dd04f3472b2e9b67d3eb65fb1.png)

**Updated!**

I had `data-remote="true"` on the input. I removed it. Now it’s just result without any “document” (no content) as in image, but the first console log.

---

<div class="post-metadata">

**Author:** ![SylarRuby](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/sylarruby/32/106_2.png) [@SylarRuby](https://discuss.hotwired.dev/u/SylarRuby)\
**Post date:** [August 19, 2020, 8:27pm UTC](https://discuss.hotwired.dev/t/live-search-without-the-entire-page-reload-just-the-partial/1339/7 "2020-08-19T20:27:50Z")

</div>

I got it!!! OMG!!! I needed to put

```auto
<%= render partial: 'list', locals: {customers: @customers} %>

```

In a file with the same name as the action’s name! Sweet! Works! OMG!! Thank you!!!

---

<div class="post-metadata">

**Author:** ![welearnednothing](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/welearnednothing/32/502_2.png) [@welearnednothing](https://discuss.hotwired.dev/u/welearnednothing)\
**Post date:** [August 19, 2020, 9:35pm UTC](https://discuss.hotwired.dev/t/live-search-without-the-entire-page-reload-just-the-partial/1339/8 "2020-08-19T21:35:02Z")

</div>

Heyyyyy right on, glad to hear it! 🎉
