Is it possible to have a Surface component that instantiates itself from within (conditionally, of course)
I was trying to do this in mail_node.sface
:
<MailNode :for={{ id <- MailClient.children(@mail_client, @id) }}
props={{ id: id,
meta: MailClient.mail_meta(@mail_client, id),
mail_client: @mail_client }} />
Ant it complains:
** (CompileError) lib/liv_web/live/mail_node.sface:8: you are trying to use the module LivWeb.MailNode which is currently being defined.
If this is not possible, how do I model a recursive data structure like a tree with surface component?
Hi @derek-zhou!
Currently, Surface does not allow using a component recursively. However, you can work around this issue by moving the recursion to a separate function. For instance:
defmodule Tree do
...
@doc "The root node"
prop node, :map
def render(assigns) do
~H"""
{{ render_node(@node) }}
"""
end
defp render_node(node) do
~H"""
<div>
{{ node.name }}
<div :for={{ child <- node.children }}>
{{ render_node(child) }}
</div>
</div>
"""
end
end
4 Likes
Thanks. I will try this and the other venue I am trying, which is to use a MacroComponent that hide the recursion inside.
@msaraiva 's example can be simplified to:
defmodule Tree do
...
@doc "The root node"
prop node, :map
def render(assigns) do
~H"""
<div>
{{ @node.name }}
<div :for={{ child <- @node.children }}>
{{ render(%{node: child}) }}
</div>
</div>
"""
end
end
Although the component cannot be recursively defined, the render/1
method can call itself recursively, with a made up assigns. This method works as long as the component has no state; because state-less component is basically just a render/1
function with maybe some pure function helpers.
I don’t know how to achieve the equivalent of recursive stateful component though. I don’t have a usage case so far.
Note: If you are going to call render/1
manually with a made up assigns like above, it is best to pass in @socket too, like {{ render(%{socket: @socket, node: child}) }}
The reason is components pass @socket implicitly, and many things need @socket; so without the @socket in the assigns it can be very confusing.