Live Search Using Liveview

LiveView provides real-time user interaction within an application. Some use cases where LiveView fit excellently in an application are input validation, button click, autocomplete and live search. The rationale is that the LiveView module consists of callback functions responsible for tracking and rendering relevant changes without a full page reload. For example, once a button is clicked, an event is pushed down the web socket to the LiveView process. It handles that event using the handle_event callback function, and if the state of that process changes, LiveView will re-render relevant changes to the page.

LiveView Callback Functions mount -> Assigns the initial state of the LiveView process, for example, fetching a list of suppliers and assigning the list to the socket handle_event -> changes the state of the process, i.e. filtering the list of suppliers by name render -> returns a new view for the updated state.

Scenario one, the database will contain a supplier table. We will need a function that takes in a supplier attribute and pipes it into the supplier’s changeset. The changeset will take the supplier struct and performs validation, filtering and casting, especially when parameters are sent through a form, API etc.

Secondly, we need a function that lists all suppliers in the database.

In the third step, we will need a form with a phx_change form binding to handle real-time form changes of the user search. This form is passed to LiveView’s handle_event callback function which performs a database lookup for suppliers that contain the search query. The returned suppliers struct is assigned to the socket prompting LiveView to call the render function, pushing the update to the browser.

def create_supplier(attrs) do
  %Supplier{}
  |> Supplier.create_supplier_changeset(attrs)
  |> Repo.insert()
end
def list_suppliers do
  Supplier
  |> Repo.all()
end
def do_mount(_params, _session, socket) do
  suppliers = list_suppliers
  socket =
  socket
  |> assign(:socket, socket)
  |> assign(:suppliers, suppliers)
  |> assign(:search, "")

  {:ok, socket}
end
<%= form_for :form, "#", phx_change: :do_filter, as: :search_phrase %>
  <div>
    <input
      name="search_phrase"
      autocomplete="off"
      value="<%= @search_phrase %>"
    />
  </div>
</form>

<div>
  <%= for supplier <- @suppliers do %>
    <div><%= supplier.name %></div>
  <% end %>
</div>
def handle_event("do_filter", params, socket) do
 {:noreply, bind_search_options(socket, params)}
end
def bind_search_options(socket, params) do
  search = params["search_phrase"]
  suppliers = Suppliers.search_supplier_by_name(search)

  socket
  |> assign(:suppliers, suppliers)
  |> assign(:search, search)
end
def search_supplier_by_name(search) do
  list_suppliers
  |> Enum.filter(fn supplier ->
   String.contains?(String.downcase(suplier.name), String.downcase(search))
  end)
end

Kristian Usifoh

🖥 Software developer. React, Elixir and ExUnit