starkeepers

starkeepers

Liveview re-rendering entire form instead of one input

hello - in the (very) simplified example below, liveview re-renders the entire form whenever (the datalist of) one input changes, thereby losing the as-yet-unsubmitted inputs. Is there a way to re-render only the input which actually changes (and hence preserving the as-yet-unsubmitted inputs)?

(The data sent over the socket does look correctly restricted to the datalist, so it looks like the unwanted re-rendering is happening on the js/browser side of things - observed both in firefox and chrome.)

  def mount(_params, _session, socket) do
    {:ok, assign(socket, q: "")}
  end

  def render(assigns) do
    ~H"""
      <form method="post">
        <input type="checkbox" name="c[]" value="cm"> check me
        <field>
          <input type="text" name="q" list="autosuggest" phx-change="update-q" 
              placeholder="type only after clicking the checkbox" autocomplete="off"/>
          <datalist id="autosuggest">
            <option value={"#{@q}-#{@q}"} />
            <option value={@q} />
          </datalist>
        </field>
      </form>
    """
  end

  def handle_event("update-q", %{"q" => q}, socket) do
    {:noreply, assign(socket, :q, String.upcase(q))}
  end

In this example, clicking on the checkbox to check it and then starting to type in the text input unchecks the checkbox, losing user input.

Thank you!

Most Liked

chrismccord

chrismccord

Creator of Phoenix

It’s not a bug. You need to either track the form changes with phx-change="..." and assign the checkbox value, or you need to tell us to ignore the inputs with phx-update="ignore">

starkeepers

starkeepers

hey @muelthe – maybe it helps if I give a more realistic use-case rather than a trimmed down minimal example as above. Imagine you have a form to upload books. The user starts by typing the title of the book, then maybe the year it came out, the topic, etc. and after that the author(s) of the book. You already have 90% of the autors in your database, so you want to help the user by providing an autocomplete on the author text input. At every keystroke, ecto will search through the authors table, and offer matches to the user so they don’t have to type in the whole author.

The form might look something like this, assuming each field is its own function component for simplicity of the code below (but it works exactly the same for our purposes if we don’t use function components):

  def render(assigns) do
    ~H"""
      <.form phx-submit="save" etc ... >
        <.title title={@title} etc ></.title>
        <.year ... ></.year>
        ... more fields here ...
        <.author_autocomplete suggested_authors={@suggested_authors} etc></.author_autocomplete>
        ... yet more fields ...
        <%= submit "Continue" %>
      </.form>
    """
  end

  def author_autocomplete(assigns) do
    ...
    <input type="text" phx-change="search-author" list="author-datalist" name="author" ... >
    <datalist id="author-datalist">
        <%= for author <- @suggested_authors do %>
          <option value={author} />
        <% end %>
    </datalist>
    ...
  end

  def handle_event("search-author", %{"author" => query}, socket) do
    ...
    {:noreply, assign(socket, :suggested_authors, result_of_ecto_search)}
  end

As usual, the form sends data for all the field upon submit, triggering handle_event("save", params, socket). So far, it sounds innocuous (I hope), but consider what will actually happen:

The user types in the title, the year, and whatever other fields, then starts typing into the authors field. As soon as they type the first character (or two), the search-author handler is invoked, which populates the suggested_authors assign, which triggers a re-render. Server-side, liveview is smart so it only renders the author_autocomplete() component inside the form, ignoring the others, and only transmit the suggested_authors down the socket, leaving all other fields out.

But when the data reaches the browser, things suddenly go very wrong for the end user: the liveview javascript uses the suggested_authors data to recalculate the html for the entire component, based on a cached version of the data used to render the earlier version of the component + the diff received from the server. The recalculated html now replaces the user-visible version of the component, thus … wiping out all the data that the poor user typed in (but offering the autocomplete suggestion).

There are a few things one can do to avoid this, but I was asking above if liveview could natively avoid it without us working around it, by not replacing the entire component (in the DOM) and rather replacing only the minimal dom nodes that were actually changed (leafs of the dom). The answer is “no” :slight_smile:

Possible options to work around this include:

  • encapsulate the autocomplete field into its own live_component (I haven’t tried this yet, so for now I’m only assuming that updating an embedded live component does not trigger a re-render of the parent component.)
  • add a phx-update="ignore" to each of the other fields, as @egze suggested
  • revert to ‘dead’ routes (ajax-style) to update the list of suggestions (the datalist in the example above), thus bypassing liveview re-rendering
  • add a phx-change to each field (actually, on the whole form, thanks @cmo for the correction), thus keeping all state on the server in the assigns as soon as the user fills in that field

And probably more :slight_smile:

At least that’s my understanding for now, I’m happy to be corrected. I hope that helped!

egze

egze

Seems like a bug to be honest.

As a workaround, this will fix it, but I think it should not be needed:

<input type="checkbox" name="c[]" value="cm" phx-update="ignore" id="my-checkbox">

Edit
Found this comment from @chrismccord Checkboxes check state can't be overwritten by server reliably · Issue #615 · phoenixframework/phoenix_live_view · GitHub

I interpret it as - checkbox state should also be maintained by the LiveView.

Where Next?

Popular in Questions Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lists...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

Other popular topics Top

New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43806 214
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement