larshei
How do you tackle user notifications and other data in every LiveView?
The Task
Brace yourselves, wall of text is coming
For my current project I have a need to show notifications and small indicators (to highlight areas with new information) to users. Most of these indicators or notifications should be available/visible on any page as long as the user is logged in.
Why a discussion?
This is probably a very common thing to have/need, but searches in the forum turned up pretty old or unrelated posts. Maybe I was not good at using the search function.
I would be very interested in how you people would tackle (or have tackled) a scenario like this. While the motivation behind this post is to answer the question on how to do this for my project, I am aiming to have more of a discussion than just asking a question.
What do we need to do?
Assuming both an indicator and a popup notification are desired, it is necessary to
- Figure out if there are unread notifications on page load to display indicators and
- Subscribe and react to updates on every page for continuous updates
Out goals?
- Hit the database(s) as rarely as possible
- Manage as little state as possible - or better: none
- Duplicate as little code as possible
- Do not touch each LiveView file
- Keep the concept simple
Discussion: How can we do that?
Assuming all pages are LiveViews.
This is the part that I would really like to discuss.
The following outlines some possible solutions I could come up with (some of them I have never tried) and what I think the upsides and downsides are, but I might be mistaken there, in which case I would be happy to improve my understanding.
Page load
Simple/Stupid: Hit the database on every load
Simple to set up: slap the database request into a Plug or on_mount and assign the result. Use the assign in your template.
No state management
Does not scale well as it requires unnecessary resources … although most applications would probably be okay with this.
Makes the page feel less snappy or even slow.
Not that much of an argument, but: Somehow feels bad to do it like this :P.
Is there a way to keep some of the data when navigating within a live_session?
Go through a cache (Agent/GenServer/ETS) to get the the database result
When a user is first active after some time, grab their data from a database, store it somewhere and do incremental updates
Pretty simple to set up (on_mount)
Faster after first load
Trades computing for memory
Possible bottleneck?
State management: The cache needs to subscribe to updates and invalidate its data if needed. Not clear how long we should wait before data can be dropped and probably not trivial to keep state up to date?
Start a User twin that lives as long as the User is active (Presence)
Basically the same as the above, but with
better isolation and less bottleneck-y?
When using e.g. Phoenix Presence we have a clear indicator on when we can drop the twin and its data.
Nested, sticky LiveView
Kind of like the twin: Separate process, that fetches the data on initial load and can then act as a cache.
Requires no lifecycle handling, as Phoenix would cover that
Only useful when navigating within a live_session(?), as the nested LiveView would otherwise be reloaded as well.
For each new window or browser tab we start from scratch
If communication with the parent LiveView is needed, for example to update different indicators on the page, we actually do not gain much, as we still have to touch every LiveView? ← Could make use of JS events here.
Live Updates
Subscribe each LiveView to a PubSub
Pretty simply concept
Lots of code copying for duplicate handle_info() and mount() content.
Subscribe on_mount, add handle_info() to use XYZ, :live_view
Pretty simple setup
Compiler will complain that handle_info() functions are not grouped.
Move handle_info() to separate macro/quote, place it manually next to other handle_info()
Compare to above:
Compiler is fine
Need to add code to each LiveView
Nested, sticky LiveView
Basically like the player in LiveBeats · Phoenix Framework Have a separate LiveView that fetches the
No copied code: Put all handle_info() and subscriptions in one place
Simple setup: Add the LiveView to your template
Popup notifications should be simple on each page with this through e.g. absolute positioned UI elements.
Only useful when navigating within a live_session(?), as the nested LiveView would otherwise be reloaded as well.
If communication with the parent LiveView is needed, we actually do not gain much, as we still have to touch every LiveView.
JavaScript event listener in app.js
Not entirely sure about this one, as I avoid JS wherever possible. Maybe an option is to add a JS event listener to app.js and then update specific DOM elements as needed (e.g. giving notification indicators an ID and show/hide them).
Should also work with dead views.
Separate Phoenix Channel
I have no experience with this.
Should also work with dead views.
Bonus Question/Scenario
If some of the pages users can visit (and have notifications on) are also publicly available pages and therefore can be viewed without having an actual user associated, how would you suggest to handle that? Separate LiveView? Separate Template?
for everyone who got here without skiping ![]()
Most Liked
sa-mm
If I’m understanding you’re explanation of these options, I think you might be interested in taking another look at the docs for attach_hook/4. There are both on_mount and handle_info lifecycle stages, so you should be able to define how you receive the message there. You could do something like the following in a single “hook” module:
- In on_mount, when connected, subscribe to the relevant topic.
- In on_mount, load from db (or your cache or whatever) the initial data.
- In hand_info, receive the message and assign it to the socket.
The compiler does not complain about this.
codeanpeace
To add on to this, you can attach a lifecycle hook via on_mount onto an entire live session/3 from your router.
# lib/my_app_web/router.ex
defmodule MyAppWeb.Router do
use MyAppWeb, :router
# pipelines, plugs, etc.
live_session :default, on_mount: MyAppWeb.InitAssigns do
scope "/", MyAppWeb do
pipe_through :browser
live "/", PageLive, :index
end
end
...
end
derpycoder
We can embed a live view in the root layout, that will show up on all pages using that layout!
Using live_render
It can be used to embed a live view in a static view as well, but in that case page load will happen on every navigation.
For example, see the heartbeat indicator at the top right of the page.
It’s part of the HeaderLiveView, and doesn’t reset on page route change. (If navigation happens by live_patch!)

Only useful when navigating within a live_session(?), as the nested LiveView would otherwise be reloaded as well. (This is my observation as well, but I don’t mind as I’ll keep the pages that belong together, in the same session and layout anyways, probably)
As for talking to parent live view, it is coming in later versions of LiveView I guess.
Popular in Discussions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









