sezaru

sezaru

Add `prepend?` option to `on_mount/1` macro or allow scoped `live_sessions`

Currently, there are two ways to add on_mount hooks to a liveview, you either add them in the live_session macro, or inside each liveview using the on_mount/1 macro.

The order that they will be executed is first the hooks in the live_session call, and then the on_mount/1 calls inside the liveview in order.

Also, live_session calls don’t support having other live_session calls inside them.

This is a bit limiting in some scenarios and make a lot of duplicated code in some complex routers.

Example

In my route, I want to have the following hooks being attached to the liveview on_mount:

/auth” route (contains routes to sign_in and sign_up):

  • Hooks.GetLocale
  • Hooks.GetTimezone
  • {Hooks.Authentication, :not_authenticated}

"/admin"route (contains routes that needs the user to be logged):

  • Hooks.GetLocale
  • Hooks.GetTimezone
  • {Hooks.Authentication, :authenticated}

Notice that both routes require the Hooks.GetLocale and Hooks.GetTimezone on_mount hooks.

So either I need to write two live_session calls that repeats the common hooks in my router:

scope "/auth", Live.Auth do
  live_session :not_authenticated,
    on_mount: [Hooks.GetLocale, Hooks.GetTimezone, {Hooks.Authentication, :not_authenticated}] do
    ...
  end
end

scope "/admin", Live.Admin do
      live_session :authenticated,
        on_mount:  [Hooks.GetLocale, Hooks.GetTimezone, {Hooks.Authentication, :authenticated}] do
    ...
  end
end

Or I can add Hooks.GetLocale and Hooks.GetTimezone to my live_view function (because every liveview in my system should execute these two hooks anyway):

def live_view do
  quote do
    use Phoenix.LiveView,
      layout: ...

    on_mount Hooks.GetLocale
    on_mount Hooks.GetTimezone

    unquote(html_helpers())
  end
end

And then my route becomes simpler:

scope "/auth", Live.Auth do
  live_session :not_authenticated, on_mount: {Hooks.Authentication, :not_authenticated} do
    ...
  end
end

scope "/admin", Live.Admin do
  live_session :authenticated, on_mount: {Hooks.Authentication, :authenticated} do
    ...
  end
end

This second example is better IMO since I don’t need to repeat a long list of on_mount hooks that are common to all my liveviews, but they just works fine as long as no hook in my live_session requires some value first set by these hooks set in the live_view function.

The reason is that the live_session hooks will be executed first and then the ones set in the live_view function. So, for the /auth route, the order would be:

  1. {Hooks.Authentication, :not_authenticated}
  2. Hooks.GetLocale
  3. Hooks.GetTimezone

If Hooks.Authentication requires the locale value, then it will fail because the Hooks.GetLocale is being executed after it.

I see three ways to fix this, the first one is the first example I shown in this post, basically I repeat all the common hooks in all live_session calls I have in my router.

This option can become a chore really fast if I have a bunch of common hooks that I want to add, makes the router harder to read and more verbose.

A second solution would be to add a prepend? option to the on_mount/1 call, that way, I can write it like this:

    on_mount Hooks.GetLocale, prepend?: true
    on_mount Hooks.GetTimezone, prepend?: true

And with this I would get this order of execution:

  1. Hooks.GetTimezone
  2. Hooks.GetLocale
  3. {Hooks.Authentication, :not_authenticated}

The third solution would be to allow live_session (or a simplified version of that that only allows to set the on_mount option) to be defined inside another live_session, for example:

live_session :common, on_mount: [Hooks.GetLocale, Hooks.GetTimezone] do
  scope "/auth", Live.Auth do
    live_session :not_authenticated, on_mount: {Hooks.Authentication, :not_authenticated} do
      ...
    end
  end

  scope "/admin", Live.Admin do
    live_session :authenticated, on_mount: {Hooks.Authentication, :authenticated} do
      ...
    end
  end
end

IMO that would be the best approach since it will allow to scope it the same way we do with the scope macro.

What do you thing about this suggestion? Do you know another way to achieve the same?

Most Liked

sodapopcan

sodapopcan

I wanted something similar before and this is very nicely thought-out post so apologies for the very short response: For hooks that are always required, I’m content using a single LiveHooks module that attaches and loads all the necessary hooks/assigns. Because they are all laid out in a single function, it doesn’t bother me so much that they aren’t explicitly listed in my router. While including them in MyAppWeb.live_view is not a bad option, it’s best to be in the clear that odd time yo don’t want to load them for a special-case route. It’s also where people except them to be.

sodapopcan

sodapopcan

Oh ya, that’s all I meant. I’ve actually never found myself in the situation where I have specific hooks I want in different scenarios, everything has pretty much always been global based on authentication, so I’m not actually super qualified to answer here due to lack of experience.

As for typing for every live_session, I don’t totally mind this myself as it keeps with Elixir’s “be explicit” mantra. But I don’t usually have a lot of them so again, I’m unexperienced there in terms of what that would actually be like.

Unrelated: your username stuck out and I realized it was because I finally tried Flashy yesterday :smiley: So far it’s working well but will report any feedback. It’s a nice lib, thanks!

Where Next?

Popular in Proposals: Ideas Top

sbennett33
When building a component library, it is often useful to give users the ability to customize the underlying element or component to use. ...
New
markevans
Hi! I feel like Phoenix is slightly missing a trick when it comes to front-end Javascript libraries like React, Svelte, etc. I feel tha...
New
woylie
We are seeing a lot of warning logs like this: navigate event to "https://someurl" failed because you are redirecting across live_sessio...
New
mikesax
On a Rails/Turbo site, the first page is typically loaded using http GET and then sockets are used navigate and replace HTML content for ...
New
cevado
IEx is a very powerfull shell and it would be awesome to have all this power integrated inside a code editor. Clojure enables something l...
New
bartblast
This could resolve to {[a: 1, b: 2]}. Was it ever considered to allow such syntax? Notice this: {:abc, a: 1, b: 2} and this: my_fun(:abc,...
New
kccarter
This is likely a feature request unless we’re overlooking something, but it would be a nice improvement to the developer experience if th...
New
Oliver
One common problem we face in constructing lists is that there is (AFAIK) no support for conditionally inserting members into list declar...
New
dibok
Hi, I’m trying to use phoenix.js in my Qt QML project which has it’s own buildin JavaScript engine. Problem is that (what I googled so f...
New
eagle-head
Hi everyone, I’ve been researching Content Security Policy Level 3 support in Phoenix and wanted to share my findings and a proposal for...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
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
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31265 143
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52673 488
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement