Hey, just getting started with Phoenix + LiveView, coming from Django and React. I’m a bit overwhelmed by the various ways of approaching templating, so apologies in advance if there’s an obvious solution here.
What I’m looking for is the ability to hook into a LiveView render() call, such that I can calculate derived assigns to pass to the template (i.e. so that I can modify assigns). I’d like to do this to keep my LiveView state small and shaped like an entity store, and derive template usage in the render method, as in React.
I understand that function components support this, when leveraging the ~H sigil, but I’m looking to keep my template separate from the LiveView. As an example, the auto generated core components:
def flash(assigns) do
assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end)
~H"""
... template here
What I’m looking to do:
@impl true
def render(assigns) do
assigns = derive_view_model(assigns)
actual_render("template.html.heex", assigns)
end
As I understand, LiveView automatically generates a render() function for the template of the same module name. What I’d like to do is understand how to reproduce that method so that I can customize it, or otherwise hook into it.
My current approach is to hook into assign() calls manually - e.g. socket |> delete_entity(:alerts, alert) |> derive_view_model() . However even in my first LiveView page I have forgotten to do this several times, so I’m hoping there’s a better way.
I have a function called set_state in pretty much every LiveView which basically does this, yes you have to manually call it where needed. But it’s pretty flexible, you could have different ones depending on how expensive they are, and only call them when needed. But there is no automatic way of doing it.
You can use attach_hook/4 to run a function after handle_params, handle_event and handle_info. This will ensure that these derived assigns are set all the time. Use on_mount for hooking into mount
Thanks for the reply - I’m glad I’m not the only one. It stands out to me that it is possible to accomplish this by only leveraging function components (including for render()) - for consistency, it’d be great to be able to update assigns for template file rendering, too.
I have a function called set_state in pretty much every LiveView which basically does this, yes you have to manually call it where needed. But it’s pretty flexible, you could have different ones depending on how expensive they are, and only call them when needed. But there is no automatic way of doing it.
What you can do, and that’s similar to Emacs’ advice system, is to use the socket as a container for assign hooks but you need to redefine assign. Something like the following:
# The second argument is when the hook is called.
# Could be before, after, around and so on.
advice_assign(socket, :after, &derive_assigns/1)
defp derive_assigns(%{assigns: %{...}} = socket) do
# Use Phoenix.Component.assign/2,3 here
end
defp assign(%{private: %{assign_hooks: hooks}} = socket) do
# Process the hooks accordingly.
end
You could do something similar to what Surface does with quoted_mount. Just move the super call to run before you do the state handling:
defmacro before_compile(env) do
quoted_mount(env)
end
defp quoted_mount(env) do
defaults = env.module |> Surface.API.get_defaults() |> Macro.escape()
if Module.defines?(env.module, {:mount, 3}) do
quote do
defoverridable mount: 3
def mount(params, session, socket) do
socket =
socket
|> Surface.init()
|> assign(unquote(defaults))
super(params, session, socket)
end
end
else
quote do
def mount(_params, _session, socket) do
{:ok,
socket
|> Surface.init()
|> assign(unquote(defaults))}
end
end
end
In LiveView “assigns” are state stored in the server.
The bare building blocks you have are a module and functions (thankfully only two concepts).
The LiveView “as a whole” (~page) is a module, all logic goes into functions.
The framework will call callbacks that you implement on your module: mount, handle_*, etc.
The way I approach derived assigns is computing them as a function of their dependencies (often a private function defp inside the module and called as appropriate).
You can as well inline your templates in your LiveView module, def render(assigns), and then you can do as you wanted/saw in function components as in the CoreComponents module.
Note you need to be careful to follow the rules with regards to change tracking: common pitfalls.
Thanks for all the suggestions. Coming from a React background, I feel most comfortable with @rhcarvalho ‘s approach to inline templates. In React, I’d map liveview assigns to component state , with the render() method in each supporting derived state.