nallwhy

nallwhy

What is the best way to update a form from outside the form in LiveView?

Sometimes I need to update a form from outside the form in LiveView.

  • For UI, some inputs are need to be created outside the form.
  • Updating form when file is uploaded.

I made an simple example.

The hour input is outside the form.
When hour input is changed, select_hour event is triggered.

page: Json Media · Json Media
code: https://github.com/nallwhy/json_corp/blob/main/apps/json_corp_web/lib/json_corp_web/live/playgrounds/form_live.ex

Marked As Solved

dfalling

dfalling

To answer your question, I’ve handled this in two different ways in my application:

  1. do a Changeset.put_change(changeset, :time, time) in my event handler, so the data gets persisted into my form state. I have a hidden input like you do to ensure the value persists when other changes happen to the form.
  @impl true
  def handle_event("select_hour", %{"hour" => hour_str}, socket) do
    time = Time.new!(hour_str |> String.to_integer(), 0, 0)

    changeset =
      socket.assigns.form.source
      |> Ecto.Changeset.put_change(:time, time})

    {:noreply, assign_form(socket, changeset)}
  end
  1. if I’m in a live component that I want to be more reusable (not requiring the parent to implement a handle_event), I call a assign(socket, :value, hour), and use a small hook I wrote that will trigger the form change event so Phoenix’s standard form handling will detect it and populate it.

But as your current example is, I think both of these are overkill. I would instead improve the select’s data so it already has the times as values. Then there’s no necessary conversion step and you can put the time select in the same form as the name.

  defp time_options() do
    0..23
    |> Enum.map(fn hour ->
      label = "#{hour}:00"
      value = Time.new!(hour, 0, 0)

      [key: label, value: value]
    end)
  end

This returns this data which you can use in your select:

[
  [key: "0:00", value: ~T[00:00:00]],
  [key: "1:00", value: ~T[01:00:00]],
  ...
  [key: "23:00", value: ~T[23:00:00]]
]

Note: I haven’t tested this- I’ve render %Time{} into input and it works fine. Assuming it also works as a value for select.

Also Liked

nallwhy

nallwhy

Thank you!

I solved it with params of Changeset following your first suggestion, because put_change/3 doesn’t validate the changes.

  @impl true
  def handle_event("select_hour", %{"hour" => hour_str}, socket) do
    time = Time.new!(hour_str |> String.to_integer(), 0, 0)

    form =
      socket.assigns.form.source.params
      |> Map.put("time", time)
      |> Routine.changeset()
      |> Map.put(:action, :validate)
      |> to_form()

    {:noreply, assign(socket, :form, form)}
  end
TwistingTwists

TwistingTwists

in a similar approach, I have a helper function that takes params, and returns updated params.
Because, I have 3 external fields. (uploads, timers etc) outside the form

becomes

  form =
      socket.assigns.form.source.params
      |> add_timer(timer)
      |> add_uploads()
      |> Routine.changeset()
      |> Map.put(:action, :validate)
      |> to_form()

defp timer(params, timer), do: Map.put(params,"timer", timer)
defp add_uploads(), do: .... 

I like your proposed solution.

dfalling

dfalling

Sure, here it is:

// Hook that will dispatch change event when it's updated. Useful for inputs that are
// updated by a component so they won't fire a change event.
Hooks.PublishInput = {
  updated() {
    this.el.dispatchEvent(new Event("input", { bubbles: true }));
  },
};

Note: this triggers the input event, which by itself won’t get back to LiveView. It has to be inside a managed form which will then trigger whatever change handling you have.

Last Post!

dfalling

dfalling

Sure, here it is:

// Hook that will dispatch change event when it's updated. Useful for inputs that are
// updated by a component so they won't fire a change event.
Hooks.PublishInput = {
  updated() {
    this.el.dispatchEvent(new Event("input", { bubbles: true }));
  },
};

Note: this triggers the input event, which by itself won’t get back to LiveView. It has to be inside a managed form which will then trigger whatever change handling you have.

Where Next?

Popular in Questions Top

New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36654 110
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New

We're in Beta

About us Mission Statement