So I can see in the code there are default “data flash” properties already setup. In the top level view I can access either @flash or assigns.flash, however, this has disappeared from the assigns list once I am in the render function for some component called from the view?
In most ways I expect this result. However, looking at the code I wonder if you wanted it to be otherwise? Should we pass down the flash value to sub components ? (or am I just missing the obvious way to do it?)
At present I can achieve the end result by wrapping my top level view with a <Context put={{flash: assigns.flash}} and then do a get in the sub component(s). However, I can’t create a “prop flash, :map”, which would allow passing as a simple property, because there is this already existing data definition? (obviously I can create a different name - my point was that the name looks reserved for some use?)
G’day, just wondering if it’s possible to pass components as props to components?
Say I’ve got a Button component, and some Icon components. Sometimes I’d like to show an Icon component with the text on a Button… this works well:
<Button>
<Icon.Pencil class={{"-ml-1", "mr-2", "h-5", "w-5"}} />
Add New
</Button>
The API I’d like to provide on the Button component should hopefully allow something like this:
<Button icon={{ Icon.Pencil }} title="Add New" />
Then the button component can add those utility classes to the Icon. I see there is a :module property type, but when I set the icon prop to that, I need some way to ‘instantiate’ it as a component.
Well, this was pretty simple in the end, but I’d love to hear if it’s unsupported/frowned upon/likely to blow up in my face! My Button component ended up looking something like:
defmodule Component.Button do
use Surface.Component
prop title, :string, required: false, default: ""
prop icon, :module, required: false, default: nil
slot default, required: false
def render(assigns) do
~H"""
<button>
{{ get_icon(assigns) }}<slot>{{ @title }}</slot>
</button>
"""
end
defp get_icon(%{icon: icon}) when icon != nil do
icon.render(%{class: ["-ml-1", "mr-2", "h-5", "w-5"]})
end
defp get_icon(_) do
""
end
end
Hi!
Is there a way to access a prop in mount/1 of a Surface.LiveComponent? Essentially I am passing the currently logged in user from my live view as a prop and would then like my component to fetch extra information from the database, for which I would need to access that user id. At the time mount/1 is called, though, it does not seem like the assigns are available?
Right now I am just loading the information in the view itself, but that does not encapsulate the concerns perfectly.
Remember that overriding update function requires you to socket = assign(socket, assigns) (because this is default behaviour).
This way you can also change the loaded data if the prop should change as well. It’s your codes responsibility to not load excessively, so check if socket.assigns.your_data_prop holds the data before you load and assign to socket again.
Edit:
In general the send_update combo with update/2 is a very powerful way to model self contained components.
In SurfaceBootstrap I have a public api as such:
# External API
def show(id) do
send_update(__MODULE__, id: id, action: :show)
end
def hide(id) do
send_update(__MODULE__, id: id, action: :hide)
end
With my update/2 looking like this:
def update(assigns, socket) do
socket = assign(socket, assigns)
socket =
case assigns[:action] do
nil ->
socket
:show ->
push_event(socket, "bsn-show-modal-#{assigns.id}", %{})
:hide ->
push_event(socket, "bsn-hide-modal-#{assigns.id}", %{})
end
|> assign(:action, nil)
{:ok, socket}
end
Last week, Surface v0.3.0 has been released and it’s now available on hex.
This version improves the Surface Catalogue API which was previously released in v0.2.0 and also introduces a new surface compiler that allows autoloading colocated JS hooks.
We are happy to see that Surface is becoming more and more popular. There’s big a chance Surface is already part of many developers stacks, so it becomes essential to support these developers to have a stable version 1.0 of Surface.
Let’s not be afraid of words, our ambition is to make Surface the best possible platform for creating LV projects! To move forward on this path, we’re pleased to announce that @Malian and @miguel-s have joined our core team and will be helping us to improve Surface.
If you would like to shape the future with us, report issues or simply need help, feel free to join us on the slack channel or on github!
To answer my own question, I think components cannot share data assigns. So to factor out the flash display out in a component, I have to use a stateless component and pass the @flash in as a prop. Inside the component I can do:
phx-value-key="error" phx-click="lv:clear-flash"
as in plain LiveView. and it will clear the flash assign. The whole thing is reactive, so the passed in prop is changed as well.
This has been fixed. If you try out the version on master, you should be able to define a prop named flash in any component (live or not). Thanks for reporting this issue.
@msaraiva, I found myself using :attrs and :props more and more, instead of specifying attrs and props individually. As I understand it, :attrs are for html elements only, and :props are for components only. My question is why do we need both constructs? It is not like there can be any confusion?
If for example we only have :props for both html elements and components it is just one less thing to remember and the components will feel more like super elements as it is intended to be?
This week I’ve been pondering whether to introduce Surface to our project or not. Our front-end devs seem to be happy with how close this is to what they’re used to from Vue etc., so that’s great.
I’ve read up everything I could (including this thread) and it seems there is a lot of interest in rendering Surface components without LiveView. I understand that this is not in the cards at the moment, so I want to ask the opposite question.
How terrible would it be if I decide to build our project exclusively with LiveView just so we can use Surface? It is a new project so there would be no rewriting needed. It’s mostly a question of introducing additional complexity. Before looking into Surface my estimate was that we would need LV to build out some parts, maybe even the majority, but not everything.
It doesn’t need to be exclusively one way or the other.
I personally no longer go the “dead view” route for new projects with Phoenix. With that said, I still have a few classic http endpoints where it doesn’t make much sense to port them over to LiveView (think auth pages).
I’ve been keeping tabs on Surface ever since it surfaced . It’s such a joy to work with and complements LiveView so well, that I can’t imagine myself developing LiveView apps without it at this point.
That’s what kept me away from Surface at first. I’ve wanted to stay as close as possible to LiveView, considering it hasn’t reached 1.0 yet, without adding an extra layer of complexity on top of it. I was wrong on that call. Just as I was wrong about Tailwind and it’s approach to CSS (it’s one of those things you have to try and keep at it to understand its benefits).
Thanks for the insight. And I will definitely adopt the “dead view” term.
This is exactly what I had in mind. We have a login screen that uses form fields, that would otherwise use the exact markup as the ones inside our live views. Do we bite the bullet and just suffer the duplication between the markup in this regular EEx template and our Surface components?
That’s what I did for the /login, /signup, /password-reset etc views. I have them hard-coded with classic Phoenix.HTML code until it will be possible to bring in Surface stateless components. They’re rather simple in nature, so the hardest thing was to keep the style in sync with the rest of the app (how an input/button/form is styled since you can’t re-use the components where these are set).
How do you do it? I think you still need to do like a page redirect to set the cookies, so at one point the LiveView has to redirect to a controller, which performs session operations, and then goes back to LiveView, right? That’s how I do it but maybe I miss on something…
I don’t use cookie. I use LocalStorage to store the JWT token client side and send it back to LV on mount for it to verify.
If the token check out, go ahead. If not, push_redirect to a simple LV backed login form.