Concrete examples of when to use live_patch, live_redirect, push_redirect, push_patch?

I’ve been reading the docs lately on LiveView and I’m having trouble figuring out when to use the above functions to change pages.

Can someone please provide a few real world use cases on when you should use a specific one and if possible how they would relate to using their regular non-LV counter parts.

It sort of feels like you might use live_redirect in a template when you want to go from page A to B, such as navigating between pages. I guess similar to link without LV (which is confusing to me because there’s also redirect without LV).

Then there’s live_patch for maybe modifying the state of an existing LV page, but this is where things fall apart in my head. When you would do this vs using live_redirect?

Then for push_redirect, this is probably the equivalent to using redirect without LV in a controller action, such as when you submit a form successfully right? But if that’s the case, why is live_redirect named that instead of something like live_link?

And for push_patch, I guess it’s similar to live_patch but used in the LV instead of a template?

Some clarification and guidance would be much appreciated. Especially if you can tie in specific use cases to specific function calls for things like nav bars, pagination, updating a tiny part of a page, various form actions (submitting with invalid fields vs successful), etc…

3 Likes

I’m interested in this as well, I haven’t been following LiveView too closely and haven’t worked with these functions too much.

For push_patch however, I do have an example. You’re correct that it is used on the server side to update the state of the LiveView. An interesting trick I learned in this blog post was using handle_params/3 along with a server side state change to generate modals.

The blog post hasn’t been updated for the navigation changes in LiveView 0.7 but I have updated my implementation to use the new navigation functions. This example app is deployed and linked on the Github, you just have to manually navigate to the /room/<room_name>/admin route to see the modals in action…

2 Likes

Also, on a related topic, when should you use live_component vs making a regular live view, and are live components without state the same as template partials in a non-LV app?

So many questions! Answers related to practical examples would be great. The docs are very much written from a POV of describing the API, not applying it. It makes it kind of hard to understand while learning LV.

1 Like

This is also basically a question I have. Live view applications can totally use regular partials, so when is it important to use a stateless component vs a regular partial? Is it purely an optimization / code organization thing?

Do you use render in that case and put the partial in your templates/ directory? This wasn’t ever mentioned in the docs. I didn’t even think it was possible, because we have live_render too, I thought that was supposed to replace render to some extent.

It is 100% possible, and it is 100% unchanged from ordinary views. live_render is not a replacement for render at all. live_render is used when you want to embed a new / sub liveview in a page.

This is something on the back of my mind as well, and these are my thoughts after having implemented an LiveView application for a hackathon this past weekend.

As all good answers, it depends. I’ll first explain a bit of what I did and how I structured it, and then we’ll discuss some use cases I came up with while tinkering with LiveView/Components.

Disclaimer: it’s my first experience with LiveView so I’m open to scrutiny/suggestions! Take this with a grain of salt :slight_smile:

What I did was basically a full blown single-page-application with 5 screens. I used the LiveView layout to render a live_component that contained the live_redirect to 5 different LiveViews.

# Component that renders the live_redirects
defmodule MyAppWeb.NavComponent do
  use GaminvestWeb, :live_component

  def render(assigns) do
    ~L"""
      <%= live_redirect to: Routes.live_path(@socket, MyApp.PageOneLive) do %>
        Page 1
      <% end %>
      <%= live_redirect to: Routes.live_path(@socket, MyApp.PageTwoLive) do %>
        Page 2
      <% end %>
    """
  end
end
# lib/myapp_web/templates/layout/live.html.leex
<div class="layout">
    <%= live_component @socket, MyApp.HeaderComponent, classname: "layout__header", page_title: @page_title %>

    <div class="layout__main">
        <%= @inner_content %>
    </div>

    <%= live_component @socket, MyApp.NavComponent, classname: "layout__navbar", page_title: @page_title %>
</div>

I feel that you should reach for a LiveComponent when you want to separate a complex piece of UI from the main LiveView and you need to share data between the main LiveView and the Component. I say this because the above could have been achieved without using LiveComponent at all through render/2 as stated in LiveView docs, my most viewed and loved page of the weekend <3.

# lib/myapp_web/templates/layout/live.html.leex
<div class="layout">
    <%= render "header.html", assigns %>

    <div class="layout__main">
        <%= @inner_content %>
    </div>

    <%= render "navbar.html", assigns %>
</div>

Why did I use LiveComponent? Well, I overlooked this and LiveComponent just worked, so I moved on. But in hindsight, it made more sense to use render/2 for this particular example. However, the advantage of using LiveComponent is it’s hability to handle events and have state!

Think of LiveComponents as a kind of function that encapsulates a complex piece of UI under a parent LiveView. They can be stateless or stateful, but they are always tied to a parent LiveView. This complicates things a little bit, since you can send data up to the parent.

Most of the time, you’ll want to pass data from the LiveView down to the LiveComponent. This is how I learned in React, and imo makes your interactions easier to reason about. However, unlike React, Phoenix gives you a “event bus” through PubSub! So you could trigger changes using PubSub and read those changes up in the parent LiveView. These are all documented in the LiveComponent docs.

If you’re familiar with React, you can think of LiveComponents as being React components. LiveComponents with an :id are stateful, akin to React components that use setState or useState, and LiveComponents without an :id are akin to “dumb” React components, that is, components with no state.

Tl;dr: IMO a LiveView should have one concern to worry about. Group functionality that is closely related domain-wise under a LiveView, and use LiveComponents to handle complex piece of functionality under your LiveView. live_redirect means changing domains, i.e. is related to other LiveViews, and live_patch means changing functionality in the same LiveView, i.e. is related to LiveComponents.

3 Likes

Partials and stateless components are pretty much the same.

At the end of the day, regardless if you invoke link/2, live_patch/2,
and live_redirect/2 from the client, or redirect/2, push_patch/2,
and push_redirect/2 from the server, the user will end-up on the same
page. The difference between those is mostly the amount of data sent over
the wire:

  • link/2 and redirect/2 do full page reloads

  • live_redirect/2 and push_redirect/2 reloads the LiveView but
    keeps the current layout

  • live_patch/2 and push_patch/2 updates the current LiveView and
    sends only the minimal diff

An easy rule of thumb is to stick with live_redirect/2 and push_redirect/2
and use the patch helpers only in the cases where you want to minimize the
amount of data sent when navigating within the same LiveView (for example,
if you want to change the sorting of a table while also updating the URL).

5 Likes

Where does mount come into play between redirect and patch?

For example, in the docs I read mount only gets executed once when loading a LV, and if you patched between content A and B in the same LV (such as your sorting example), mount wouldn’t get run a 2nd time.

But does that mean it’s not possible to live_patch between 2 different LVs since each of them have their own mount? How would that work in practice?

Is there anything worth thinking about for the “pretty” part of that. Like, performance wise or any gotchas? Also from the docs, it’s not clear where the partial’s file should live. Should it be in the same directory as the LV? Typically partials are in the templates directory.

It is remounted on redirect, not remounted on patch. That’s the main distinction between them.

So you can’t patch across different LiveViews. If the client emits a patch but it is another LiveView, it falls back to a redirect.

They have to be in the templates directory, since the partial belongs to a View (and not the LiveView). I will clarify this.

1 Like

Notably, I’ve largely ditched the traditional view structure used by Phoenix controllers / views / templates in favor of:

my_app_web/
  pages/
    shipments/
      show/
        show.html.leex # the template
        show.ex # the live view
        data.ex # a data fetching helper
        view_helper.ex # the normal phoenix view helper
        components/ # components relevant only to this page
          ...

It makes this whole process way easier. I don’t really like Live in the module name anyway, it holds not a lot of value. The shipment show module name would be MyAppWeb.Pages.Shipments.Show, so it gets named just like any other Elixir module. All the files for a page are all in one nice spot, and you can make a new page by just copying and pasting any existing page folder.

4 Likes

I only played briefly with live view, once about a year ago, and once in the past weekend, and I pretty much went for a similar structure, with the only difference being that I didn’t use Pages. This weekend, while experimenting, I used the name Controller for the live view module. So I ended up with something like MySystemWeb.PageA.Controller, MySystemWeb.PageB.Controller, etc.

I find it much saner to navigate through the code, and as you say the things which are frequently read or changed together are in the same place. I also think that the word Live is basically noise. Whether something is a live view or not is imo an implementation detail, and doesn’t belong to the module name.

In the future I plan to do the same thing with controllers. I already did something similar for my blog, but that’s a very simple case so I ended up consolidating everything behind a single controller.

2 Likes

Ah, so then typically unless you have very specific requirements (like wanting iframes in your page to not be manipulated) you would live_redirect between all things that would normally be seen as “controllers” in a regular app. Like going from a blog index page to a contact page, or even something like going from listing commits to listing branches in GitHub’s UI as an example (tabs when looking at a repo)?

But wouldn’t this also mean some benefits that Chris talks about in his keynote of live view are not really benefits? For example, he talks about not having to look a user up on every page transition like you normally would with a controller plug.

Wouldn’t you end up looking up and assigning a current_user in the mount function of each LV, which would then get executed and looked up from the DB on every live_redirect? Basically in this case this assigns is comparable to a plug that sets a current_user.

If you were breaking apart a template to make it more readable would you pick render or a stateless component as a best practice?

What type of modifications did you have to make in your app’s various Phoenix config files for it to look for controllers, templates, views and other things in that directory?

Also have you talked to any of the core team members on maybe recommending this style as the default, or as what the generators generate?

I think one cool thing about Rails is 95% of the apps you see feel the same at a structural level. You can get pretty productive in any code base just by knowing Rails. This also helps you in the long run because after 3 months of not touching a code base, you know where to go.

Are you concerned that if you go way off the beaten path and drastically change how you organize your Phoenix project, then it’ll make it difficult for others to work with your code base? Especially so if out of 100 developers, we end up with 30 different styles of file organization strategies.

Likely one of the issues here is [quote=“nickjanetakis, post:15, topic:30751”]
What type of modifications did you have to make in your app’s various Phoenix config files for it to look for controllers, templates, views and other things in that directory?
[/quote]

All I had to do was:

# in MyAppWeb
  def page_view do
    quote do
      use Phoenix.View,
        root: "#{__DIR__}/templates/",
        path: "",
        namespace: ControlTowerWeb
  ... # other stuff that is copied from `def view`

Which reminds me that I actually put the templates in a templates directory within that page.

Then in the view_helper.ex file you use MyAppWeb, :page_view.

Not yet.

Sort of. Rails works great when you have < 50 controllers, models, etc. As you grow to more than that it becomes extremely unwieldy. This is part of what I like about Phoenix’s use of contexts. It creates some organization within the data side of the application.

This is an effort to do the same thing on the UI side.

Not at all. “All the stuff for the page is here” has been super easy for people to get used to. We’ve got folks who are familiar with Elixir, and folks who are just starting having come from Rails / React and everybody seems to find it pretty straight forward.

Where would your controller go with that structure, and how would that pan out when you would usually have 1 controller with a show action + other actions in 1 file?

It depends on what you consider to be transitions. For example, in a regular app, whenever you paginate, re-order a table, search results, etc, you have to fetch the current user, current organization and what not. With LV now you can use phx-click or live_patch for those, and those won’t do the refetching.

In my app I even have a settings page with different tabs, each tab is a component within the same LV, so navigating between those won’t reload either. So the benefit is definitely there for a lot of your interactions and transitions.

Yes, we have a helper called assign_defaults that we basically call on all LV. The docs on master (not yet published) have an example of this.

I would go with a component because if I want to make it stateful in the future, I am already halfway there.

What made you choose to use patch there instead of redirect?

If you were building an elaborate settings page with a number of sections, you’d probably end up having separate controllers for this in a non-LV app right? Think something like Twitter’s settings where you have account, privacy, notifications, accessibility, etc. settings.

Would you mind gisting your settings LV and how all of the components interact together in a way where your single settings LV doesn’t end up being a massive file with handle params and events for many many different things?

To be very honest, it was a happy accident. We started with a single page, then it got complex on the code wise, so we broke them into components, and then it was complex on the UI, so we broke them into tabs (pretty similar to Twitter’s).

All of our LiveViews follow the Phoenix convention, this one is something like this:

live/
  user_settings/
    index.ex
    index.html.leex
    change_email_component.ex
    change_password_component.ex
    totp_component.ex
    ...

We are doing collocated templates for LiveViews and embedded templates for components.