**Name**  
Berenice Medel  
[@bemesa21](https://twitter.com/bemesa21)

In this post, we’ll develop a dynamic list component using the new LiveView Streams and the enhanced form features introduced in LiveView 0.18/0.19. Get ready to discover how to create a responsive list component with interactive capabilities like adding, editing, and deleting items. Fly.io is a great place to run your Phoenix LiveView applications! Check out how to [get started](/content/docs/elixir/index.html)!

You developed a component that lets you add and remove items in a “has_many” relationship. Take, for example, a shopping list where it can _have many_ items. You’ve got a cool form that lets you send data for new items, and it works really well:

However, you’re still not entirely happy with the result. When you make a mistake while adding an item to the list, there’s no way to edit it. How can you make the list items editable right in the list component, without the need for a modal?

Well, what if we use individual forms for each list item instead of a single form to add and associate them with the list? That’s a simple task, we can render as many forms as we want. We’re going to use the [to_form/2](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#to_form/2) function to create form structs from changesets, and then we’re going to use those structs to render our form components.

And another thing: once we’ve fixed up our form component, we’re going to see how to use [LiveView Streams](/content/phoenix-files/phoenix-dev-blog-streams/index.html) to manage and manipulate our list items without the need to store them all in memory. Adding, editing, deleting, and resetting items in a collection has never been easier!

## Defining our component’s markup

Let’s begin by _refactoring_ [our component](/content/phoenix-files/liveview-drag-and-drop/index.html) and keeping only the header:

```elixir
defmodule ComponentsExamplesWeb.ListComponent do
  use ComponentsExamplesWeb, :live_component

def render(assigns) do
    ~H"""
    <div class="bg-gray-100 py-4 rounded-lg">
      <div class="space-y-5 mx-auto max-w-7xl px-4 space-y-4">
        <.header>
          <%= @list_name %>
        </.header>
      </div>
    </div>
    """
  end

def update(%{list: list} = assigns, socket) do
    socket =
      socket
      |> assign(
        list_id: list.id,
        list_name: list.title
      )
    {:ok, socket}
  end
end
```

In the assigns, we’re passing a struct `list`. It contains an id, a title, and a list of items, each of which has multiple attributes.

To generate these structs, their corresponding changesets, and the context functions that we’ll be using later, you can use the [phx.gen.context](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Context.html) generator:

```elixir
mix phx.gen.context SortableList List lists title:string
mix phx.gen.context SortableList Item items name:string position:integer list_id:integer
```

Here’s an example list, containing a single item:

```elixir
%ComponentsExamples.SortableList.List{
  __meta__: #Ecto.Schema.Metadata<:loaded, "lists">,
  id: 1,
  title: "Shopping list 1",
  items: [
    %ComponentsExamples.SortableList.Item{
      __meta__: #Ecto.Schema.Metadata<:loaded, "items">,
      id: 161,
      name: "chocolate",
      position: 0,
      status: :started,
      list_id: 1,
      list: #Ecto.Association.NotLoaded<association :list is not loaded>,
      inserted_at: ~N[2023-05-16 20:29:12],
      updated_at: ~N[2023-05-16 20:29:12]
    }
  ],
  inserted_at: ~N[2023-04-25 19:35:09],
  updated_at: ~N[2023-04-25 19:35:09]
}
```

Now, we need to iterate over the items of the list and render the forms. But before we dive into that, let’s update our assigns to include a form per item:

```elixir
def update(%{list: list} = assigns, socket) do
  item_forms = Enum.map(list.items, &build_item_form(&1, %{list_id: list.id}))

socket =
    socket
    |> assign(
      list_id: list.id,
      list_name: list.title,
      items: item_forms
    )
  {:ok, socket}
end
```

Let’s see the content of the `build_item_form/1` function:

```elixir
defp build_item_form(item_or_changeset, params) do
  changeset =
    item_or_changeset
    |> SortableList.change_item(params)

to_form(changeset, id: "form-#{changeset.data.list_id}-#{changeset.data.id}")
end
```

We receive an `%Item{}` and create a changeset from it. Then, we use the [Phoenix.Component.html.to_form/2](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#to_form/2) function. This handy function converts a data structure into a [Phoenix.HTML.Form](https://hexdocs.pm/phoenix_html/3.3.0/Phoenix.HTML.Form.html), which we can use to render our [form/1](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#form/1) components. Note that the `id` of the form has the `list_id` and the `id` of the item interpolated.

Check out how [to_form/2](https://www.phoenixframework.org/blog/phoenix-1.7-final-released#:~:text=New%20Form%20field%20datastructure) allows change tracking on individual inputs in our forms!

As a result, we have an assign called `:items` that holds the `Phoenix.HTML.Form` structs containing our element data. We iterate through each item in our assign and render a `simple_form/1` for each of them:

You can find the `simple_form/1` component—and many others—in `core_component.ex`

```elixir
def render(assigns) do
  ~H"""
  <div class="bg-gray-100 py-4 rounded-lg">
    <div class="space-y-5 mx-auto max-w-7xl px-4 space-y-4">
      <.header>
        <%= @list_name %>
      </.header>
      <div id={"#{@list_id}-items"} class="grid grid-cols-1 gap-2">
        <div :for={form <- @items} id={"list#{@list_id}-item#{item.id}"}>
          <.simple_form
              for={form}
              phx-change="validate"
              phx-submit="save"
              phx-target={@myself}
              class="min-w-0 flex-1 drag-ghost:opacity-0"
              phx-value-id={form.data.id}
            >
            <div class="flex">
            </div>
           </.simple_form>
        </div>
      </div>
    </div>
  </div>
  """
end
```

We define the events triggered on change or data submission, and we pass the item ID to these events using the `phx-value-*` bindings.

**Tip:** Make sure you’re using `LiveView 0.19` or a newer version, as it was not possible to send `phx-value-*` values with forms before this update.

Now, let’s add elements to the body of our item form. We start by adding a button to change the status of our element:

```elixir
def render(assigns) do
  ~H"""
  <div class="bg-gray-100 py-4 rounded-lg">
    <div class="space-y-5 mx-auto max-w-7xl px-4 space-y-4">
      <.header>...</header>
      <div id={"#{@list_id}-items"} class="grid grid-cols-1 gap-2">
        <div :for={form <- @items} id={"list#{@list_id}-item#{item.id}"}>
          <.simple_form ...>
            <div class="flex">
            <button
               :if={form.data.id}
               type="button"
               class="w-10"
               phx-click={JS.push("toggle_complete", target: @myself, value: %{id: form.data.id})}
             >
               <.icon
                 name="hero-check-circle"
                 class={[
                   "w-7 h-7",
                   if(form[:status].value == :completed,
                     do: "bg-green-600",
                     else: "bg-gray-300")
                 ]}
               />
            </button>
            </div>
          </.simple_form>
        </div>
      </div>
    </div>
  </div>
  """
end
```

This button is displayed conditionally when the list element has an `id`, indicating that it already exists in the database and its status can be edited. Additionally, we apply conditional classes to the `<.icon>` based on the item’s status, to change its color.

Now, let’s add the most important part, the text input to edit each list item:

We add two text inputs to send the parameters of our item: the `:name` and the `:list_id` to which it belongs:

```elixir
def render(assigns) do
  ~H"""
  <div class="bg-gray-100 py-4 rounded-lg">
    <div class="space-y-5 mx-auto max-w-7xl px-4 space-y-4">
      <.header>...</.header>
      <div id={"#{@list_id}-items"} class="grid grid-cols-1 gap-2">
        <div :for={form <- @items} id={"list#{@list_id}-item#{item.id}"}>
          <.simple_form ...>
            <div class="flex">
              <div class="flex-auto block text-sm leading-6 text-zinc-900">
                <input type="hidden" name={form[:list_id].name} value={form[:list_id].value} />
                <.input
                  field={form[:name]}
                  type="text"
                  phx-target={@myself}
                  phx-key="escape"
                  phx-keydown={
                    !form.data.id &&
                     JS.push("discard", target: @myself, value: %{list_id: @list_id})
                  }
                  phx-blur={form.data.id && JS.dispatch("submit", to: "##{form.id}")}
                />
              </div>
            </div>
          </.simple_form>
        </div>
      </div>
    </div>
  </div>
  """
end
```

Let’s take a closer look at the elements we added. First, we include a hidden input field to store the id of the list to which the element belongs. This allows us to send it as part of the parameters for the `validate` and `save` events by setting the `value={form[:list_id].value}` attribute.

Next, we introduce a slightly more complex `<.input>` component with event options. By using the `phx-key` and `phx-keydown` attributes, we specify that pressing the escape key triggers the `discard` event sent to the server.

We have two ways to save changes: pressing Enter to trigger the `submit` event or allowing changes to be automatically saved when the input loses focus.

For elements that already exist in the database and are modified, the `phx-blur` binding comes into play. It automatically submits the form when the input loses focus, ensuring that changes are saved seamlessly.

Lastly, we add a button with an icon to delete existing elements from the database:

```elixir
def render(assigns) do
  ~H"""
  <div class="bg-gray-100 py-4 rounded-lg">
    <div class="space-y-5 mx-auto max-w-7xl px-4 space-y-4">
      <.header>...</header>
      <div id={"#{@list_id}-items"} class="grid grid-cols-1 gap-2">
        <div :for={form <- @items} id={"list#{@list_id}-item#{item.id}"}>
          <.simple_form ...>
            <div class="flex">
              <button
                :if={form.data.id}
                type="button"
                class="w-10 -mt-1 flex-none"
                phx-click={
                  JS.push("delete", target: @myself, value: %{id: form.data.id})
                  |> hide("#list#{@list_id}-item#{form.data.id}")
                }
              >
                <.icon name="hero-x-mark" />
              </button>
            </div>
          </.simple_form>
        </div>
      </div>
    </div>
  </div>
  """
end
```

We trigger the `delete` event while simultaneously hiding the form of the item we wish to remove.

We now add the final section to our component. We include two buttons: one to add a new item to the list, and another to delete all items from the list.

```elixir
def render(assigns) do
  ~H"""
  <div class="bg-gray-100 py-4 rounded-lg">
    <div class="space-y-5 mx-auto max-w-7xl px-4 space-y-4">
      <.header>...</header>
      <div id={"#{@list_id}-items"} class="grid grid-cols-1 gap-2">
        <div :for={form <- @items} id={"list#{@list_id}-item#{item.id}"}>
          ...
        </div>
      </div>
      <.button phx-click={JS.push("new", target: @myself, value: %{list_id: @list_id})} class="mt-4">
        Add item
      </.button>
      <.button
        phx-click={JS.push("reset", target: @myself, value: %{list_id: @list_id})}
        class="mt-4"
      >
        Reset
      </.button>
    </div>
  </div>
  """
end
```

We sent events to the server using [JS.push](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.JS.html#push/1). Both the `reset` and `new` events receive the `list_id` as a parameter.

Awesome! We’ve finished the markup for our component!

We can easily render it by passing a list struct like the one we saw above:

```elixir
def render(assigns) do
  ~H"""
  <div id="lists" class="grid sm:grid-cols-1 md:grid-cols-3 gap-2">
    <.live_component
      :for={list <- @lists}
      module={ComponentsExamplesWeb.ListComponent}
      id={list.id}
      list={list}
    />
  </div>
  """
end
```

But before we tackle handling all the events we defined, how about we optimize memory usage by using `Streams`? With just a few tweaks, we can implement this feature and ensure we’re not storing all the list elements in memory.

## Converting to Streams

To optimize memory usage, let’s start by making a small change to our assigns. We assign the stream items using the [stream/4](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#stream/4) function:

```elixir
def update(%{list: list} = assigns, socket) do
  item_forms = Enum.map(list.items, &build_item_form(&1, %{list_id: assigns.id}))

socket =
    socket
    |> assign(
      list_id: list.id,
      list_name: list.title,
      items: item_forms
    )
    |> stream(:items, item_forms)

{:ok, socket}
end
```

Next, we define the required `phx-update` DOM attribute on the parent container where the item collection is rendered. The items are now accessed via the new assign `@streams`, and we consume the stream using a comprehension:

```elixir
def render(assigns) do
  ~H"""
  <div class="bg-gray-100 py-4 rounded-lg">
    <div class="space-y-5 mx-auto max-w-7xl px-4 space-y-4">
      ...
      <div
        id={"#{@list_id}-items"}
        class="grid grid-cols-1 gap-2"
        phx-update="stream"
      >
        <div :for={{id, form} <- @streams.items} id={id}>
        ...
        </div>
      </div>
      ...
    </div>
  </div>
  """
end
```

Now we’re all set to handle the events we defined earlier!

## Fly.io ❤️ Elixir

Fly.io is a great way to run your Phoenix LiveView app close to your users. It’s really easy to get started. You can be running in minutes.

## Add, delete, update, reset Streams

We’ll be defining five events ([new](/content/phoenix-files/dynamic-forms-with-streams/#new/index.html), [validate](/content/phoenix-files/dynamic-forms-with-streams/#validate/index.html), [save](/content/phoenix-files/dynamic-forms-with-streams/#save/index.html), [delete](/content/phoenix-files/dynamic-forms-with-streams/#delete/index.html), [reset](/content/phoenix-files/dynamic-forms-with-streams/#reset/index.html)). Our main focus is to explain how we can use streams to reflect changes in our list. We won’t be diving into functions that interact with the database.

To get started, it will be helpful to define a helper function that creates forms already associated with the `list_id`:

```elixir
defp build_empty_form(list_id) do
  build_item_form(%Item{list_id: list_id}, %{})
end
```

Keep in mind this function, as well as the one we defined earlier. We’ll be using them in our upcoming steps!

### New

To add a new element to the list, we can use the function we just defined to create an empty form. Then, we can insert this form into the stream using the [stream_insert/4](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#stream_insert/4) function:

```elixir
def handle_event("new", %{"list_id" => list_id}, socket) do
  {:noreply, stream_insert(socket, :items, build_empty_form(list_id), at: -1)}
end
```

To insert the new form at the end of the list, we use the `:at` option provided by the `stream_insert/4` function.

### Validate

To display the errors of our item, we need to modify the already rendered form in the client and insert a new form that includes the errors from the changeset.

One important detail to note is that in order for our component to recognize that it should display the changeset errors, we need to add an `:action` to it. To achieve this, we make a slight modification to the `build_item_form/4` function that we defined earlier:

```elixir
-defp build_item_form(item_or_changeset, params) do
+defp build_item_form(item_or_changeset, params, action \ nil) do
  changeset =
    item_or_changeset
    |> SortableList.change_item(params)
+   |> Map.put(:action, action)

to_form(changeset, id: "form-#{changeset.data.list_id}-#{changeset.data.id}")
end
```

The action can be any random atom, but hey, let’s keep things clear and name them sensibly.

Now let’s see how to handle the event and use this new `:action` parameter:

```elixir
def handle_event("validate", %{"item" => item_params} = params, socket) do
  item = %Item{id: params["id"] || nil, list_id: item_params["list_id"]}
  item_form = build_item_form(item, item_params, :validate))
  {:noreply, stream_insert(socket, :items, item_form}
end
```

First, we generate a new `%Item{}` that includes the item `id` sent from the text input and the `list_id` sent using the `phx-value-id={form.data.id}` parameter. Next, we call our `build_item_form/4` function with the `:validate` action and insert the item into the stream using `stream_insert/4`.

### Save

When attempting to save a new item, we may receive one of two possible responses. First, when an item is successfully inserted, we clear the new item form and replace it with a fresh empty form. We also insert a new form containing the persisted data. Second, if an error occurs, we display the relevant errors to the user.

Let’s now delve into the implementation details of these actions.

```elixir
def handle_event("save", %{"item" => item_params}, socket) do
  case SortableList.create_item(item_params) do
    {:ok, new_item} ->
      empty_form = build_empty_form(item_params["list_id"])

{:noreply,
       socket
       |> stream_insert(:items, build_item_form(new_item, %{}))
       |> stream_delete(:items, empty_form)
       |> stream_insert(:items, empty_form)}

{:error, changeset} ->
      {:noreply, assign(socket, :form, build_item_form(changeset, %{}, :insert)))}
  end
end
```

Great! We can chain together the different functions of the stream using the pipeline operator.

On the other hand, the `save` event may be triggered by one of the forms that have already been saved in the database, indicating an update of the item. We can identify this by pattern matching, receiving a non-null `item_id`:

```elixir
def handle_event("save", %{"id" => item_id, "item" => params}, socket) do
  todo = SortableList.get_item!(item_id)

case SortableList.update_item(todo, params) do
    {:ok, updated_item} ->
      {:noreply, stream_insert(socket, :items, build_item_form(updated_item, %{}))}

{:error, changeset} ->
      {:noreply, stream_insert(socket, :items, build_item_form(changeset, %{}, :update))}
  end
end
```

### Delete

Deleting items from a stream is easy too!

For this we have [stream_delete/3](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#stream_delete/3) which also receives changesets as a parameter:

```elixir
def handle_event("delete", %{"id" => item_id}, socket) do
  item = SortableList.get_item!(item_id)
  {:ok, _} = SortableList.delete_item(item)
  {:noreply, stream_delete(socket, :items, build_item_form(item, %{}))}
end
```

You can also use the `stream_delete/3` function to `discard` the form of the new element when needed:

```elixir
def handle_event("discard", params, socket) do
  {:noreply, stream_delete(socket, :items, build_empty_form(params["list_id"]))}
end
```

### Reset

We have the option to remove all items from a stream, which is perfect for our reset button:

```elixir
def handle_event("reset", params, socket) do
  empty_form = build_empty_form(params["list_id"])
  {:noreply, stream(socket, :items, [empty_form], reset: true)}
end
```

We simply need to reconfigure our stream by passing the option `reset: true`. Additionally, we can include a list of new elements to be inserted, which in this case would be at least one empty form.

The features we explored today unlock a world of exciting new possibilities for apps development with LiveView. `LiveView Streams` revolutionize collection handling and memory optimization, simplifying tasks such as adding, editing, and deleting items. Furthermore, the optimizations brought by `to_form/1` enable efficient manipulation of individual inputs without the need for full form re-rendering. This simple yet immensely powerful function opens up new avenues for form usage, expanding the potential of your applications.

Check out [this repo](https://github.com/bemesa21/components_examples) to see these game-changing features in action. We used [our previous learnings](/content/phoenix-files/liveview-drag-and-drop/index.html) to create an even more impressive component!

A big shoutout to Chris McCord for sharing [the incredible example](https://github.com/chrismccord/todo_trek) that inspired these posts and for patiently answering any questions about the exciting new concepts in Phoenix and LiveView.
