mhdtrifork

mhdtrifork

Responsibilites of components in a phoenix liveview

Hi there, im pretty new to Elixir, Phoenix and Liveview. I come from an app developer background.

Im having a little hard time, wrapping my head around, how to properly compartmentalize (is that a word?) an advanced view, and which responsibilities falls to the liveview, and which falls to the live_component.

How the view was structured: (several live views, each implementing the header, above its own content)
Before

And how im thinking of structuring it now:
after

Because every tab was its own liveview previously, the presentation of specific modals, were each handled by their own “tab” liveview (using patch, and toggling visibility using live_action).

When refactoring this I want to rewrite the old live views into live_components. However, im unsure of wether or not it makes sense keep the toggling of the modals, in the components, or if this functionality should be moved to the “mother” liveview.

My gut feeling and experience from other platforms and languages, tells me that a “view” should handle navigation, and that its best to delegate this responsibility to the controller. However there are a lot of modals, which would mean that my liveview, will get cluttered by many functions. (there will be 9 tabs)

My current plan was to have the component be “dumb” and just fire a send self() from it, like what is shown in the docs here: docs
But if im piping the events through the component to the liveview, does it even make sense for it to be a live_component, or could I just as well use a function component? (im a little lost on when to use what, and for what purpose)

I need good suggestions for how to architecture a screen like this, and comments about best practice in the phoenix/liveview world.

Bonus info: This is an admin tool, where each tab is a specific view, for working and tweaking specific parts of a project.

Most Liked

soup

soup

I think if your state is complex enough to divide it up, you should put that in a live_component as they can manage their own state, otherwise its normally best in a function component. I think this is where your head was already at anyway.

Here is a layout in a recent project that is similar in structure to yours, just as an example I guess.

In this, each “green” live component manages its own structs & form – specific to that live component and passes data back up to the parent. This could all be done with function components but the main live view would get very bloated. Technically the tab bar is a function component.

So I think this is a spin on your “after”, where instead of nesting the tab content inside a tab component, I cut out the middle man. The “which tab are we on” state isn’t sufficiently complex to need a separate live component in my case. I have used this kind of layout many times on complicated multi-stage forms.

All the tabs are rendered into the page at once and just display: hidden in this case. This does have an impact on the initial page load time if the data structure (well, if the html derived from the datastructure) is complex, so if you can, I would try and only render one at a time. In this case it’s simpler to just load them all instead of passing data up and down and re/de-hydrating all the time and it’s an internal tool so the extra 100-300ms initial render time isn’t too problematic.

So I guess my advice is to start with functional components until the state being tracked is complicated enough that you want to modularise it; at which point you can put it into its own live_component and focus your state changes through messages. This send pattern is really great when you want to only send “valid” data back up to the parent as you can localise all the validation cruft to section-specific changesets & “views” (live_components).

Hope some of that is helpful?

But if im piping the events through the component to the liveview, does it even make sense for it to be a live_component, or could I just as well use a function component? (im a little lost on when to use what, and for what purpose)

If I understand what you mean here, I would not pipe the “handle_params” event down to the component, just use that to determine which to render?

soup

soup

You can split any module into submodules and import/etc those, or you could split a live view into a collection of live components (:thinking:).

Not sure I can judge the validity of any approach much more from the outside beyond what I wrote before – showing tabs and showing modals both seem like “page content” and so should be probably controlled by the “page” (which is our liveview here), separating them also means you can reuse the modal (not the modal wrapper but maybe the actual modal form) in other places without requiring a tab to control it. I just can’t see the reasoning of tying them together but I don’t have the whole context.

Honestly it sounds like you’re over thinking it a bit? Build it one way and discover why you hate it and build it the other way (and discover that way also sucks for other reasons :wink:).

DirkDiggler

DirkDiggler

I’ve found another work around just now using the metadata field of the live macro in the router.

:metadata - a map to optional feed metadata used on telemetry events and route info, for example: %{route_name: :foo, access: :user}. This data can be retrieved by calling Phoenix.Router.route_info/4 with the uri from the handle_params callback. This can be used to customize a LiveView which may be invoked from different routes.

With a route of:

live "/tabs/a", TabsLive, :index, metadata: %{tab: :tab_a}
live "/tabs/a/:id/edit", TabsLive, :edit, metadata: %{tab: :tab_a}
live "/tabs/a/new", TabsLive, :new, metadata: %{tab: :tab_a}
live "/tabs/b", TabsLive, :index, metadata: %{tab: :tab_b}
live "/tabs/b/:id/edit", TabsLive, :edit, metadata: %{tab: :tab_b}
live "/tabs/b/new", TabsLive, :new, metadata: %{tab: :tab_b}

Then in TabsLive handle_param you can fetch the tab from the metadata specified above by doing:

# I don't think the host really matters here, but not 100% sure
%{:tab => tab} = Phoenix.Router.route_info(AppWeb.Router, "GET", URI.parse(uri).path, "127.0.0.1")

You can then plop the tab in the assign and use it to display/interact the tab component.

This makes it a little less cumbersome in the sense that you don’t need to parse anything specific out of the path by hand. Albeit, the computational work of parsing the URL is the same.

Where Next?

Popular in Questions Top

Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

Other popular topics Top

baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31494 112
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

We're in Beta

About us Mission Statement