Phoenix 1.7.0 final is out!

Thanks to all contributors! You all contributed in making this release wonderful.

A minor step for semver but a major leap for Phoenix devs.

Hear hear! Read read! The book is highly recommended at this forum. Can’t wait to see what is behind it’s beautiful cover.

5 Likes

fixed. Thanks!

3 Likes

This is great news, already updating!

Any tips for converting routes from the non-verified format to the ~p verified routes sigil? I have about 700 route calls in my code-base to convert :grimacing:

Update: I see this note about updating routes under “Optional updates” in Upgrading from Phoenix v1.6.x to v1.7.0

While these changes are a big step forward for new applications, it probably makes sense to maintain your current application conventions and skip these steps for established, large 1.6 applications.

4 Likes

I never thought I’d get this excited about streams. I don’t even like to fish…

5 Likes

and in example/generators:

Blog.list_posts/0 calls Repo.all/1 which then fetches all records, right? The stream/3 even calls for on said collection (to add generated dom_id and -1 index to a new Tuple). Where we win here when talking about stream?

From what I can see using Repo.stream/2 instead of Repo.all/2 would not give us much as per mentioned for call that returns a List, right?

I guess that after rendering said (empty) table the live stream is rendering one row at a time and sends the DOM to JavaScript which is then appended to table element, right?

So let’s say I have 1 million database entries and I want to stream them using Repo.stream/2 only when needed to have infinite scrolling. Can I do so with this new API?

2 Likes

The win for streams will come when pushing updates to LV via PubSub. It is not in the generated code but streams get us closer to that and adding the remaining PubSub bits should be easy.

11 Likes

I can confirm that I was successfully able to modify several items at once, may be not in a very efficient way, within one handle_info callback and they seem to arrive as one update to the client. This is what I did. I need to modify checkbox selections of several items at once:

socket = Enum.reduce(cells, socket, fn c, acc -> stream_insert(acc, :cells, c) end)
{:noreply, socket}

Unfortunately, in my case the payload is around 8K-14K per each round. I’m blaming Heroicons as they get included as inline svg. I show them by “if”, but I also tried “ifing” hidden / block CSS class to a surrounding div and couldn’t achieve any better results yet. Still playing.

Edit: There’s also Edit & Delete links with JS coming for each updated row - another source of a heavy bandwidth. Plus Tailwind class names, as now they are also part of each message. I think this will at some point require attention, I just don’t want to prematurely optimize while sketching the app.

4 Likes

@chrismccord Is there a known show stopper for Phoenix to send the static segments of components only once? Then over the lifespan of a session a user would build ‘a library of components’ and only dynamics have to be send for each component which was sent earlier in the session. Especially useful with rows.

Asking directly as you went to great extend to minimize data over the wire so maybe you have thought of it earlier and know the answer already.

—-

…and next we request to prebuild the component lib for CDN etc etc. One step at the time…

3 Likes

I tried to cover this in the writeup, but even ignoring the PubSub use case, we get huge wins with streams in phx.gen.live compared to the 1.6 approach. We used to push_navigate back to the PostLive.Index LV when you submitted the modal form, which would require refetching the entire listing every time you created or edited a post. Now with streams, we can push_patch back to the index LV and stream_insert the created or updated post. No extra fetching of the listing required.

Our generated context functions have nothing to say about how you’d handle pagination, so this is a little tangential to our generated code. But LV streams could absolutely be used to do “realtime feeds”, especially because you can prepend or append (or insert any any place) on demand. Hope the helps!

12 Likes

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.

3 Likes

I would hope the controllers folder structure to be more organized like live, https://elixirforum.com/t/organise-files-under-controllers-folder-similar-to-live-and-context-folder/54136?u=alaadahmed
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(
  "save_tag",
  {"tag" => %{"tag" => tag, "type" => type, "id" => id, "parent_id" => parent_id}},
  socket
) do

But I got:

def handle_event(
  "save_tag",
  %{"tag" => tag, "type" => type, "id" => id, "parent_id" => parent_id},
  socket
) 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.

6 Likes

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.

7 Likes

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.

2 Likes

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 phx.new?