Compare Phoenix 1.7 LiveView generator with 1.6

The official release note has more detail which I strongly suggest reading. But I sort out some changes from Phoenix version 1.6 LiveView generator to 1.7 if you want a direct comparison for a glance.

Code shows in this post are generated by this command in brand-new phoenix projects.

mix phx.gen.live Posts Post posts title content:text

Before the generator, let’s check core_compoents first.

  1. No more live_helpers.ex. In 1.6, if we call live generator for the first time in the project, it will generate a live_helpers.ex file. It contains a modal component for our generated CRUD. In 1.7, all components we need already exist in core_components.ex now.
  2. Since version 1.7 has TailwindCSS built in. All the code we generated and components in the core_compoents.ex file are styled with Tailwind now.
  3. All functions in Phoenix.HTML.Form have been moved to either core_components or Phoenix.Components

Index

In the index module, the only difference is we now use Phoenix.LiveView.stream/4 instead of assigning the list directly.

mount

   def mount(_params, _session, socket) do
-    {:ok, assign(socket, :posts, list_posts())}
+    {:ok, stream(socket, :posts, Posts.list_posts())}
   end

handle_event(“delete”

     {:ok, _} = Posts.delete_post(post)
 
-    {:noreply, assign(socket, :posts, list_posts())}
+    {:noreply, stream_delete(socket, :posts, post)}

There’s a new handle_info in this file. We’ll address that in the FormComponent section later.

Index template

In the index.html.heex file, the new version leverage the new table component.

+  <.table
+    id="posts"
+    rows={@streams.posts}
+    row_click={fn {_id, post} -> JS.navigate(~p"/posts/#{post}") end}
+  >
+    <:col :let={{_id, post}} label="Title"><%= post.title %></:col>
+    <:col :let={{_id, post}} label="Content"><%= post.content %></:col>
+    <:action :let={{_id, post}}>
+      <div class="sr-only">
+        <.link navigate={~p"/posts/#{post}"}>Show</.link>
+      </div>
+      <.link patch={~p"/posts/#{post}/edit"}>Edit</.link>
+    </:action>
+    <:action :let={{id, post}}>
+      <.link
+        phx-click={JS.push("delete", value: %{id: post.id}) |> hide("##{id}")}
+        data-confirm="Are you sure?"
+      >
+        Delete
+      </.link>
+    </:action>
+  </.table>

Show

No change here 😀

Show template

The show.html.heex file is similar to the index template. Besides replacing the plane template with new components, there’s no other change.

+  <.list>
+    <:item title="Title"><%= @post.title %></:item>
+    <:item title="Content"><%= @post.content %></:item>
+  </.list>

FormComponent

After creating a new item or updating an existing item in this live_compoennt, the old strategy is push_redirect to return_to after we make the change.

          socket
          |> put_flash(:info, "Post updated successfully")
-         |> push_redirect(to: socket.assigns.return_to)}
+         |> push_patch(to: socket.assigns.patch)}

In the new version, We notify parent LiveView about the new change. Then push_patch to patch(used to be return_to) instead of push_redirect. There are two kinds of outcomes depending on what the parent LiveView is.

  • Inside Index: The new handle_info in Index will stream_insert the new or changed item without updating the whole list. Then handle_params triggered by push_path updated the title and reset the post to nil.
  • Inside Show: There’s no handle_info. We only trigger the handle_params by push_path. handle_params then update the title and current item.
-      {:ok, _post} ->
+      {:ok, post} ->
+        notify_parent({:saved, post})
+  defp notify_parent(msg) do
+    send(self(), {__MODULE__, msg})
+  end

Also, in the new version, We assign form(made by to_form(changeset)) instead of a changeset because of the recent form component change.

-     |> assign(:changeset, changeset)}
+     |> assign_form(changeset)}
+  defp assign_form(socket, %Ecto.Changeset{} = changeset) do
+    assign(socket, :form, to_form(changeset))
+  end

FormComponent Template

No form_component.html.heex now. Thanks to the new compact sinple_form component, the template size is now small enough to be in the render function.

+  <.simple_form
+    for={@form}
+    id="post-form"
+    phx-target={@myself}
+    phx-change="validate"
+    phx-submit="save"
+  >
+    <.input field={@form[:title]} type="text" label="Title" />
+    <.input field={@form[:content]} type="text" label="Content" />
+    <:actions>
+      <.button phx-disable-with="Saving...">Save Post</.button>
+    </:actions>
+  </.simple_form>