Phoenix 1.7.0 final is out!

Awesome job Chris & Team, been looking forward to this release for long! Exciting stuff! The wait wasn’t too bad though as the rc releases were already stable enough for me :slight_smile:

1 Like

Not really. Stateful Phoenix.LiveComponent already does this and we can generalize it. The problem is the API with how the user can opt-in to this, because it requires tracking what was sent on both client and server (only the keys), so it is definitely not something we want to do for everything automatically.

Or alternatively we figure out a mechanism to send them as regular images and we let the browser cache do its thing.


I would hope the controllers folder structure to be more organized like live,
But ya, we still can do this manually after generating files.

1 Like

When we are using as like this:

<.simple_form for={%{}} as={:tag} phx-submit="save_tag" phx-change="validate_tag">

We should get it in our handle_event like this, okay?

def handle_event(
  {"tag" => %{"tag" => tag, "type" => type, "id" => id, "parent_id" => parent_id}},
) do

But I got:

def handle_event(
  %{"tag" => tag, "type" => type, "id" => id, "parent_id" => parent_id},
) do

Am I wrong in understanding as keyword?

It should be noted, I updated my mix file to phoenix 1.7 and after that I copied the core_components.ex from a fresh phoenix 1.7 installed

Other problem is, why we should pass value to input, when it is empty by default

<.input name="tag" label="Tag Name" id={"field-tag-#{@type}-#{@block_id}"} value=""/>

without value="" I have error, I can edit the core component or create new one to have this, but I am curious why it should be like this, Something like this is my preference

attr :value, :any, default: nil   // or , default: ""
value={Phoenix.HTML.Form.normalize_value(@type, @value)}

I see where you have a file in your directory structure, live/home_live.ex. Can you show us what the contents of it would look like?

Basically I want to know, in a project created with default settings, are you envisioning that it is utilizing the same template file as the controller-based view since everything uses function components now (and you could choose in your router whether to use a live or controller-based view), or does it have its own render or colocated template?

I’m assuming the latter, but I want to make sure I’m not overlooking anything. The generated live files follow the pattern you outlined above of having their own directory and Index module etc. Thank you!

The point of passing as is that you will use a form struct to generate the inputs for you with the correct names and values. Therefore, you must do this:

<.simple_form :let={f} for={%{}} as={:tag} phx-submit="save_tag" phx-change="validate_tag">

And then pass field={f[:foo]} to your input. Alternatively, call to_form(%{}, as: :tag) in your LiveView and pass the result of to_form as for and use field={@form[...]}. In other words, you must let <.input generate the names for you.


I think this needs some better documentation or even warnings. I just ran into this as well updating a form to 1.7.0 and removing the :let={f} as per all the new documentation around using @form. It took me a solid while till I figured out why my as="form" was no longer working.


Can you take a stab at a PR? In a nutshell, as=... works, but you need to capture the form with :let. Unfortunately, there is no way for the component to know at the moment a :let was given or not, so the best we can do is document. Perhaps the best way to go about this is to deprecate as in the long term.


At least some of these prepositions are a bit ambiguous :slight_smile:
Beginning with for, it is generally used for comprehensions so it hints naturally, hey, write this:

<.form :for={f <- @form} phx-change="validate" phx-submit="save">
   <.input field={f[:username]} type="text" />

instead of this:

<.form :let={f} for={@form} phx-change="validate" phx-submit="save">
   <.input field={f[:username]} type="text" />

I guess the confusion is coming from using :for and an atom vs for as attribute, as they look so similar.

Thanks @chrismccord and others!

Streams and the LV form optimization: long awaited great stuff!

Is there an option to skip tailwind when running

No, there’s no such option. Even with --no-assets in core_components.ex there are still tailwind classes.

If you want to see all task options call: mix help

No but have a look here to see how to remove it:

This is so awesome!

The 1.7.0 changelog didn’t do justice to LiveView stream imo. After reading the blog post about them, it finally clicked.

For me, it’s less about efficiency with large data, but more about LV having an easy way to deal with has_many/embeds_many associations in forms.

For anyone looking to automatically convert routes from the older format, here’s a mix task! :slight_smile:


Nice, but you can make it better, for example this one:

  |> Enum.filter(&(!(&1) |> String.contains?("Routes.")))
  |> Enum.reduce(%{}, fn filename, learnings ->
    test_filename(filename, learnings)

may be replaced by:

  # keep one file contents in memory
  # do not read file twice in filter
  |> Stream.filter(fn {_, contents} -> String.contains?(contents, "Routes.") end)
  # and reduce
  |> Enum.reduce(%{}, fn {filename, contents}, learnings ->
    # extra argument
    test_filename(filename, contents, learnings)

This should speed up linked code and reduce memory usage.

1 Like

It hasn’t been released yet but now there’s --no-tailwind and --no-esbuild options for mix :tada:


No need to link a specific commit as support for those flags is already mentioned for next release:

[] Support the --no-tailwind and --no-esbuild flags


I’m trying to upgrade, but I’m stuck because we had a thin wrapper around Phoenix.Endpoint.Cowboy2Handler which is now removed. I’m trying to do custom dispatch in Phoenix 1.7 as described here Phoenix.Endpoint.Cowboy2Adapter — Phoenix v1.7.2. I tried doing a thin wrapper around Plug.Cowboy.Handler the same way but I can’t get it to work. Any suggestions?

Could you create a custom plug and just add it to the endpoint module directly?