Live View - text_input render issue

Hi everyone,

I’m building a search input form and I’m now facing following an issue, see below gif image.

search_form_no_clean
What I want is, after click on fa-times icon, the input form will be an empty.

My search component looks

<%= f = form_for :query_form, "#", [phx_change: :validate_query] %>
    <p class="control has-icons-left has-icons-right">

        <%= text_input f,
            :query,
            class: "input has-padding-left-65",
            placeholder: "Search",
            value: @query
        %>

        <span class="icon is-small is-left icon__search">
            <i class="fas fa-search"></i>
        </span>
        <%= if @query != "" do %>
            <span phx-click="clean_search"
                class="icon is-small is-right icon__times"
            >
                <i class="fas fa-times has-cursor-pointer"></i>
            </span>
        <% end %>
    </p>
</form>

and live.ex

# 
defmodule ServerWeb.Patients.IndexLive do
  @moduledoc false
  use ServerWeb, :live

  def mount(_session, socket) do
    {:ok, assign(socket, query: "", found_patients: nil)}
  end

  def render(assigns), do: ServerWeb.PatientsView.render("patients.html", assigns)

  def handle_event("clean_search", _params, socket) do
    socket =
      socket
      |> assign(%{query_form: %{"query" => ""}})
      |> assign(query: "")

    {:noreply, socket}
  end

  def handle_event("validate_query", %{"query_form" => %{"query" => q}}, socket) do
    socket =
      socket
      |> assign(%{query_form: %{"query" => q}})
      |> assign(query: q)

    {:noreply, socket}
  end
end

Finding the solution, I came with a little fix. I had to add to the class at text_input following function fix_reset(@query), i.e. class: "input has-padding-left-65 #{fix_reset(@query)}", where the function is defined as

  def fix_reset(data) when is_binary(data) do
    data
    |> md5sum()
    |> Base.encode32(padding: false, case: :lower)
  end

  def fix_reset(nil) do
    ""
  end

  defp md5sum(data) when is_binary(data) do
    :crypto.hash(:md5, data)
  end

After that, the search input works as I wanted. See below:

search_form_ok_clean

Did I overlook something or is it a bug of Live View? Same behaviour I discovered also for textarea

1 Like

This is actually working as inteded. You are setting the value attribue of the input to an empty string. This does not overwrite the value in the text box. This attribute is more meant to be the default value. In fact, if you open your HTML inspector, you should see the value attribute being updated as you type and press the x.

The reason “nothing” is happening is because you are still using the same dom object. It is not getting overridden. You are just changing one of its attributes that has no effect on the actual element. While in the scenario of you adding a hashed class to the input may be changing it enough that it is deemed to need a new element.

Whether this is the “right” way of doing this, I am unsure. Personally, I would really like to see something similar to bucklescript-tea’s unique and key arguments. Basically, give them some kind of unique value, and it will change the tree inside the element if the value changes. (OvermindDL1 can elaborate a little more here in case I got that wrong)

3 Likes

Close enough, but in essence in the DOM there are two kinds of values, attributes and properties. You need to set one kind sometime, the other some other times, and in rare cases even both (to potentially different values!).

Last I checked (I keep trying to use Phoenix.LiveView but it keeps missing features I need) LiveView didn’t make a distinction on that, so I’m still using Drab (older than LiveView, does a whole lot more than LiveView, while doing everything LiveView can do thus far), so I’ll use it’s syntax for examples.

So in Drab, this template:

  <div class="blah <%= @some_calculated_class %>" @style.width="<%= "#{@the_width}%" %>">
  </div>

So in this example, the class is an attribute, it will be set to be blah plus whatever @some_calculated_class is, and will be updated in the DOM as the @some_calculated_class assign changes. The @style.width is a property setter. It is of the form of @propertypath=value where propertypath is a full normal javascript style property path, and it will set the value to the given value and update it as it changes (Drab sends extremely little data in comparison to LiveView for such things, it sends a simple command to update what changed, instead of sending the whole DOM patch like LiveView does). Drab’s model of property setting is a common pattern in a lot of javascript frameworks as well, easy to use yet powerful and efficient.

Now I don’t know how LiveView supports setting property values, but at the very least you could drop down to sending javascript itself (ew…). And get used to the pattern of however it does it because there are times where you need to set the attribute, times you need to set the property, and times you need to set both (to potentially different values), especially if you ever use custom elements or webcomponents in the HTML or a variety of javascript frameworks.

3 Likes

Hey @ondrej-tucek.

We just solved a similar thing by having a counter that we assigned to 0 on mount, and then when the “clean” event happened we incremented the counter. Then, in the template, we had

<div id="form-counter-<%= @counter %>">
<%= f = form_for :query_form, "#", [phx_change: :validate_query] %>
...
<% end %>
</div>

On reset we’d set the changeset back to empty, increment the counter, and voila, empty form. Not sure if that’s a hack or a good idea, but it worked for us!

4 Likes

Thanks all of you for the explanation and helping.